Compare commits

..

340 Commits

Author SHA1 Message Date
Tim Lingo bef36a5e3a feat(soul): Stage 1 structural audit as a real route — an annotated characterization, not a score (#91)
Neuron Soul CI / build (pull_request) Has been cancelled
Neuron Soul CI / deploy (pull_request) Has been cancelled
`runStructuralAudit` has been an advertised MCP tool with nothing behind it: the
dispatcher GET'd /session/begin and returned that unrelated session digest under
an audit tool's name. Meanwhile the failure the audit would have caught ran
silently for about three weeks — the soul reporting 103,089 nodes while the
engram, which OWNS persistence, held ~79,900, a crash discarding the difference,
and every boot reporting green throughout, because nothing in the system ever
compared the two sides.

WHAT THE PATENT SPECIFIES, AND HOW IT SHAPED THIS
  CGI provisional, 05-detailed-description.md, "Stage 1: Structural audit 430".
  Two clauses did the design work. First the four things the module evaluates:
  the density and typed distribution of causal edges; value/execution-record
  consistency; the richness and connectivity of the self-model; and wonder-
  manifest authenticity. Second, and decisively: it "produces a coherence
  assessment 432 — NOT A BINARY SCORE but an annotated characterization of the
  graph's structural properties."

  So every finding carries its numbers AND a plain-language note saying what
  they mean and how they were obtained. There is no pass/fail and no composite
  health figure, and `"score":null` is emitted explicitly so a reader cannot
  mistake its absence for an omission.

WHAT IS IN STAGE 1 (four findings)
  owner_runtime_divergence   — the motivating case. Runtime counts vs the owner's
      own GET /api/stats, the delta, and the trend against the previous audit, so
      a second call answers "is the gap growing?" rather than restating it.
  self_model_connectivity    — the three identity pillars plus the self root:
      present, content length, one-hop degree. This RETIRES the Claude-side vitals
      identity block, which lived outside the system it was checking and went on
      reporting green while the memory-philosophy pillar was absent from the live
      graph. Asking the running soul is the designed mechanism; a shell probe was
      the fourth patch on the same hole.
  typed_edge_distribution    — exact counts against the claim-10 vocabulary, plus
      density, plus a separate count of LOWERCASE near-misses ("causes" vs
      "Causes"): "the vocabulary is unused" and "the vocabulary is misspelled by
      the write paths" are different defects with different fixes.
  orphans_and_dangling_edges — the tool's own long-standing promise.

WHAT IS DEFERRED, AND WHY IT IS DATA RATHER THAN A COMMENT
  Value/execution-record consistency and wonder-manifest authenticity ship as a
  `deferred` array that MEASURES the populations they would need (Prediction and
  WonderQuestion nodes) and reports those counts as the reason. Both are ~0 today
  — WonderQuestion because of a known write/read node-type mismatch. Asserting
  value coherence or a pull-weight correlation on an empty population would be a
  fabricated result, which is worse than a stated gap.

MEASUREMENT HONESTY: EXACT WHERE CHEAP, SAMPLED WHERE NOT, ALWAYS LABELLED
  Counts, edge typing and self-model connectivity are exact. Orphan and dangling
  rates are sampled, because engram_find_node_index is a linear scan — an
  exhaustive dangling check is O(nodes x edges), ~2.2e9 string compares at today's
  scale. Samples are UNIFORM across the whole population (str_index_of_all gives
  every edge offset in one pass, so any index is O(1); json_array_get would have
  been O(n^2)), never head-of-list, and each figure ships with its own sampled /
  population / exhaustive fields. ?edge_sample= and ?node_sample= at population
  size run either check exhaustively. The real fix is an id index in the runtime.

ONE BUG THIS FOUND IN ITSELF, CAUGHT IN TEST
  http_get does not return "" when the owner is unreachable — it returns a JSON
  error object. Testing only for "" made a DEAD owner read as reachable with
  node_count 0, so the audit reported 100% divergence and named it data loss.
  Reachability is now proved by the presence of the node_count field, and the
  owner's raw reply is attached. A confident wrong answer is exactly what this
  route exists to stop.

  Edge findings need relation labels and the runtime has no edge-enumeration
  builtin, so they use the same scratch export GET /api/graph/edges already uses
  (engram_save to TMPDIR, never the owner's canonical file — #117). That is a
  large write on a large graph, so this is a manual route, not a timer; ?edges=0
  skips it.

  neuron-api.el:900-1273  handler + helpers
  routes.el:567,752       GET and POST /api/neuron/audit/structural
  mcp-wrapper/src/main.el:113,682  tool description + dispatch off /session/begin
  dist/soul.c             regenerated (1255 bodies)

Rung: E2E-VERIFIED. Soul built from this branch (gen-soul-amalgam + cc-brain,
921,192 bytes, 16 warnings, 0 errors), booted on throwaway ports 7893/7896/7897
with throwaway HOMEs against a stub owner on 7894. Three scenarios pass: owner
reachable (runtime 62 vs owner 42, delta 20 / 32.2%, trend flat on the second
call; 12/20 edges claim-10 typed, 3 lowercase near-misses; 50/62 orphans, 3/20
dangling — every figure matches the fixture by construction), owner unreachable
(reported as a finding with the raw reply, not a crash), and file mode (owner
"none", divergence undefined). Reached end-to-end through the MCP tool via a
locally built wrapper. verify-soul-contract.sh: GATE PASS, 27/27 routes +
immutability. No process left running; live :7770 and :8742 untouched (GET only).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 16:45:19 -05:00
tim.lingo 9501e4ac12 Merge pull request 'fix(engine): memories written through the soul now reach the store that owns them (closes #117)' (#134) from feat/soul-write-through into main
Neuron Soul CI / build (push) Failing after 11m27s
Neuron Soul CI / deploy (push) Failing after 14m46s
2026-08-07 21:07:14 +00:00
Tim Lingo dd952c0e46 feat(soul): write-through to the persistence owner — memories survive restart (#117)
Neuron Soul CI / build (pull_request) Failing after 14m57s
Neuron Soul CI / deploy (pull_request) Failing after 14m39s
The soul obeys half of its own ownership rule. soul.el:571-573 says "when
ENGRAM_URL is set the HTTP Engram owns persistence — the soul must NEVER write
to the local snapshot", and it doesn't. But nothing was ever built to hand the
soul's writes TO that owner: sync is pull-only (/api/sync -> engram_load_merge),
so every node created inside the soul lived in process RAM and was shed on
restart. Measured live 2026-08-07: soul node_count=102184, engram 79197.

SCOPE CORRECTION vs the earlier internal spec: engram provisional claim 17's
"pull-then-push" is a PEER-ENGRAM to PEER-ENGRAM protocol (claims 15-18 say so
explicitly). The soul is a CALLER of the database API, not a peer. Claim 17 is
NOT authority for a soul<->engram contract and is no longer cited as such. The
design here follows from the ownership rule alone.

Mechanism: a new Accessor, persist.el, is the single boundary. Writes stage a
delta to a filesystem spool and are pushed to the owner via POST /api/load-merge
— NOT POST /api/nodes, which mints a new server-side id (breaking dedup and
edges) and drops label/tier/tags/importance/confidence (verified in a sandbox:
a tier "Canonical" probe came back "Working"). load-merge preserves the id and
every field, dedups nodes by id and edges by (from,to,relation) so retries are
no-ops, and calls persist_canonical() so THE OWNER writes its own file — the
ownership rule is honoured rather than worked around.

Spool-and-drain rather than push-per-write: measured ~0.38s per load-merge at
live scale (79k nodes/176MB), and a chat turn writes 5-7 nodes. The spool is on
disk, not in process state, because the soul serves each connection on its own
pthread and a shared buffer would lose entries to a read-modify-write race. That
also buys crash recovery: writes orphaned by kill -9 are drained on next boot.

Honesty: api_persisted (the gate all 10 MCP write handlers pass through) and
mem_store now assert AT THE OWNER instead of reading back the soul's own RAM.
With the owner down a write returns {"ok":false,"error":"write_not_persisted"}
and the delta is queued — where main returns {"ok":true} for a write that dies.

Coverage: 35 node sites + 9 edge sites routed through the boundary. Deliberately
excluded, with reasons in persist.el: 4 InternalStateEvent sites (Will's own
telemetry carve-out), the boot counter and the persona (both already have
bespoke owner-side write-backs), and soul.el's 54 genesis identity edges
(file-mode only). engram_strengthen and engram_forget are NOT propagated —
load-merge cannot update or delete, and hard-deleting at the owner would fail
verify-soul-contract.sh section B.

Also fixed here:
- routes.el GET /api/graph/edges engram_save()'d straight over the owner's
  canonical snapshot.json — a read route, in a non-owner process, clobbering the
  canonical on every call. Same defect class Will removed from the engram in el
  dc39a61. Now exports to a scratch path. With this gone the soul writes nothing
  at all in HTTP mode.
- persist.el must clear the runtime's _tl_fs_read_len hint after every fs_read.
  In vendored runtime v1.0.0-20260501 that hint becomes the NEXT response's
  Content-Length, so reading a spool file mid-request made an 86-byte reply go
  out as 497 bytes with 411 bytes of adjacent heap trailing it. Caught and fixed
  at our boundary; the runtime class was fixed upstream in el 43636ae, which is
  not the pinned runtime here.

Rung: E2E-VERIFIED, discriminating. Same harness, same engram binary:
  write-through: LEG 1 PRESENT at owner, LEG 2 SURVIVED kill -9 + restart
  main:          LEG 1 ABSENT  at owner, LEG 2 LOST
verify-soul-contract.sh: GATE PASS on both builds (27/27 routes, immutability).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 12:47:31 -05:00
tim.lingo 18714e6142 Merge pull request 'fix(engine): restore multi-turn crisis escalation on the agentic path (P0, closes #129)' (#130) from fix/129-history-amplification into main
Neuron Soul CI / build (push) Failing after 14m37s
Neuron Soul CI / deploy (push) Has been skipped
2026-08-07 15:54:41 +00:00
tim.lingo 4936099c39 Merge pull request 'fix(engine): the daemon survives a client leaving, and says it is working while it works' (#127) from fix/liveness-engine-91 into main
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
2026-08-07 15:54:15 +00:00
tim.lingo f1471763f5 Merge pull request 'fix(engine): approving a researched mission completes — the resume replay read a tool id out of the conversation (BUG-42, both faces)' (#115) from fix/resume-server-tool-replay into main
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
2026-08-07 15:53:51 +00:00
tim.lingo 5850793b67 Merge pull request 'fix(engine): history keeps its provenance and its session — kills the false confession, the blank stare, and the "to.Good" seams' (#114) from fix/soul-history-provenance-20260805 into main
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
2026-08-07 15:53:32 +00:00
tim.lingo fc1745c652 Merge pull request 'feat(engine): plain chat generates at L3 — inside the safety cycle, not around it (+ crisis-path segfault fix)' (#109) from feat/soul-plain-chat-generation-20260805 into main
Neuron Soul CI / build (push) Failing after 10m39s
Neuron Soul CI / deploy (push) Failing after 14m47s
2026-08-07 15:53:08 +00:00
Tim Lingo 43d0449904 fix(engine): the agentic crisis screen reads the session's own history again
P0 SAFETY. Closes the regression we introduced in ff421d3 (2026-08-05).

ff421d3 correctly moved conversation history to a per-session key via
conv_hist_key(session_id). One consumer did not move with it: the agentic
path's L1 safety screen kept reading the anonymous "conv_history" bucket. The
desktop app always mints a session id (DaemonClient.kt:706), so history was
always written under session_hist_<id> and that read always returned "".

The half of the crisis score that receives history is the escalation half — the
one that exists for distress building across several turns, where no single
message trips the bell on its own. It scored 0 on every real conversation for
two days. Single-message hard bell was never affected.

The bitter part: the comment that line carried documented this exact bug being
fixed once already, under issue #9. The fix was right then. The rename
re-broke it, and the comment went on describing a repair that no longer held.
A comment is not a gate.

The read now goes through conv_hist_key like every other consumer, including
the plain path at soul.el:398 and the thread-anchoring read thirty lines below
it in this same handler. It is one line. The rest of this commit is structure
so it cannot happen quietly again:

  - agentic_safety_screen() owns the two decisions that were inline — which
    window the screen sees, and the screen call. Inline safety inputs are
    untestable safety inputs; that is what let a rename starve this one with
    nothing failing and nothing logging.
  - the comment above the call site now states the invariant (read window ==
    written window) instead of naming a key that can be renamed out from under
    it.

TWO-LEG PROOF, one variable — the single line state_get("conv_history") ->
state_get(conv_hist_key(session_id)):

  before  scripts/run-el-test.sh tests/test_history_amplification.el
          3. REGRESSION #129 ... FAIL  got: soft_bell  expected: hard_bell
          8 passed, 1 failed          runner exit 1
  after   same command, same tree, that one line changed
          9 passed, 0 failed          runner exit 0

Full engine rebuild from these sources is clean: gen-soul-amalgam.sh ->
1,164,103 bytes / 1226 inlined bodies (gate wants >= 1200), cc-brain.sh ->
903,096 bytes, 0 errors. agentic_safety_screen and conv_hist_key both present
in the built binary (nm: T _agentic_safety_screen, T _conv_hist_key).

Rung reached: BUILT + RUNS (discriminating test). NOT yet in a DMG and not yet
verified in the app a human opens — those are the next two rungs and neither is
claimed here.

Known and NOT fixed by this commit:
  - feat/soul-openai-tools-v2 carries the same defect independently at
    chat.el:2937 and needs the same change or a merge.
  - the defect CLASS (a read of a state key no producer writes) is still
    invisible to every gate we have. Issue #129 proposes making it a build
    error; that is the follow-on.

Closes #129

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 09:33:05 -05:00
Tim Lingo b842e82f77 test(engine): a runner for tests/, and a failing regression test for #129
tests/ has held 14 test programs for months with no way to run them. CI does
not run them. The convention printed in their own headers
(`elc soul.el && ./soul --test tests/x.el`) refers to a --test flag the El
runtime does not implement. So the tests were documentation, not gates — which
is how a P0 safety regression shipped with a test directory sitting right
there.

scripts/run-el-test.sh compiles and runs one test program. It reuses the
gen-soul-amalgam.sh discovery: elc emits only an extern prototype for a module
that has a .elh beside it, and inlines the bodies when it does not, so a test
importing ../chat.el must be compiled in a scratch tree with the headers
removed. Scratch copy on purpose — the worktree is shared. It runs the binary
under a throwaway HOME so a test can never reach the live engram.

Exit status is the gate: the El tests print failures and still exit 0, so the
runner greps for FAIL lines and for a zero assertion count as well.

tests/test_history_amplification.el pins the invariant #129 violated: the
window the safety screen READS must be the window conv_history_record WRITES.
Not "must be called conv_history" — must AGREE.

THIS COMMIT IS RED BY DESIGN. On this tree the test fails one assertion:

  3. REGRESSION #129 — agentic screen reads the session's own window
    FAIL: distress history escalates the agentic screen to hard_bell
      got:      soft_bell
      expected: hard_bell
  history amplification tests: 8 passed, 1 failed   (runner exit 1)

The next commit turns it green by changing one line. Two legs, one variable —
that is the whole point of committing the test first.

Two flaws in the older harness that this one does not copy: the idiom
`let pass_count = pass_count + 1` inside an assert function declares a local
that dies with the call, so every existing suite prints "0 passed, 0 failed"
regardless of outcome; and a test program without a `cgi` block compiles as a
'utility', which may not reference the self-formation primitives chat.el's
agentic loop calls — it fails to build on a capability violation it never
triggers at runtime.

Refs #129

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 09:32:40 -05:00
Tim Lingo 98ccbd4704 fix(engine): a client that leaves must not kill the daemon, and a long round must say it started
Round 9.1, spec §3 D + ADR 0006 items 2 and 4. Two small changes, both proven
by measurement, both E2E-verified locally against a rebuilt brain.

D1 — SIGPIPE/EPIPE survival (vendor/el-runtime el_runtime.c).
Root cause, at the layer that owns it: the whole HTTP server lives in the C
runtime; .el has no socket primitive. http_send_all() called send() with flags
0 and nothing anywhere in the runtime set a SIGPIPE disposition, so the default
disposition — terminate the process — applied. When a handler finished after
its client had gone (Tim's VM: reply at 116.9 s, client cancelled at 25.0 s),
the second of the four sends that write one reply raised SIGPIPE and the daemon
died: `exited due to SIGPIPE ... ran for 361177ms`, launchd respawn 4 ms later,
every other in-flight session's work lost, user never told.

Fix: SIGPIPE -> SIG_IGN at runtime init and at each http_serve* entry, plus
per-connection SO_NOSIGPIPE / MSG_NOSIGNAL so the guard survives an embedder
resetting dispositions. http_send_all now retries EINTR and preserves errno;
http_send_response classifies it once — a departure is logged as routine
("client left before the reply was written ... reply discarded") and ANY other
errno is logged as a real "send failed: <strerror>". Spec §5.3: the routine
case must not mask a genuine write fault, and it does not.

Proof (scratch HOME + free port, 3 disconnects mid-reply):
  round-9 shipped brain 4402179554… — DIED, exit 141 (128+13 = SIGPIPE), round 1
  round-9 sources rebuilt with this exact recipe — DIED, exit 141, round 1
  this build — SURVIVED 3/3, /health 200 after, still serving the full graph,
  three honest "client left" lines in the log naming Broken pipe / Connection
  reset by peer.

D2 — the round-start marker (chat.el, agentic_loop).
The ledger only ever appended AFTER a round returned, so a healthy first leg
produced zero progress by construction; since server-side web_search moved
inside the outbound call that leg is 60-120 s of silence, which is how a 25 s
client watchdog came to kill a healthy mission. One entry,
{"i":N,"t":"","tool":"__working__"}, written to the existing
run_progress_<session_id> ledger BEFORE each round's outbound call — the wire
shape ChatView.kt:1148 has handled as a life signal since 2026-07-13 and never
received. No new key, no new route, no new lifecycle: a strict subset of WS3
item 3. WS3's run registry is untouched and stays Will's.

Proof (live Anthropic key, real research mission, scratch HOME + free port):
  round-9 baseline — ledger EMPTY for the whole 59.7 s leg
  this build       — {"i":0,"t":"","tool":"__working__"} visible at 18.6 s of a
                     70.0 s leg; both builds returned correct ~4.9 KB answers

Regression: prompt-matrix gate 32/32 on this build (round-9 baseline also 32/32
under the same recipe, so the score is not a build artifact). Soul contract
gate PASS — 27/27 routes, immutability clean. neuron#111 miscompile guard: 0
sites in the generated amalgam this binary was compiled from.

NOT included, deliberately: the regenerated dist/soul.c. CI compiles that file,
so production stays exposed until it is regenerated — the same open ask as
neuron#111 / ui#209. The regen recipe is now known and recorded; landing it is
Will's call, per BUILD-HYGIENE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 18:23:12 -05:00
Tim Lingo dba755dcec fix(engine): resume reads the bridged tool id from the blob's own field, not from inside the replayed conversation
ROOT CAUSE (round 9; live-repro'd 5/5 this morning, both faces stub-proven by the
prompt-matrix gate). json_get is a first-substring-match scanner (strstr for
'"key":', el_runtime.c). bridge_save serialized the RAW messages array BEFORE the
tool_use_id scalar, so agentic_resume's json_get(blob, 'tool_use_id') returned the
FIRST '"tool_use_id":' occurrence inside the replayed conversation, not the saved
field. The resume guard then preferred that misread over the client's correct
call_id (its two branches both reduced to saved_use_id), attached the tool_result
to the wrong id, and Anthropic 400'd the resume ('unexpected tool_use_id found in
tool_result blocks'), surfaced as {"error":"llm unavailable"}.

ONE MISREAD, TWO FACES — whichever block owns the first tool_use_id in the array:
  FACE 1 (search-then-bridge, the Key West killer): the first occurrence is the
    first web_search_tool_result's srvtoolu_… id — every agentic turn that ran
    server-side web_search and then bridged on a client tool died on approval,
    deterministically (messages.2.content.0 … srvtoolu_…). The write itself had
    already succeeded; only the resume died.
  FACE 2 (multi-cycle missions): with no search, the first occurrence is ROUND 0's
    tool_result block — so every LATER approve/resume cycle replayed the round-0
    client id (stale-resume-id), killing multi-file missions after ~2 files.
  And the shape that PASSES on round 8 confirms the mechanism: a single-cycle
  bridge with no prior tool round has no 'tool_use_id' substring in its messages
  at all (tool_use blocks carry 'id'), so the scan fell through to the blob's own
  field and resumed correctly.

The server_tool_use ↔ web_search_tool_result pairs themselves replay intact — the
defect was a cross-field misread of the blob, the same first-match-scanner class
as BUG-6 (approve 'content' matched inside tool_input, 2026-07-17) and round 8's
citation-block fix.

THE FIX, the pattern not the spot:
  1. bridge_save writes every json_safe'd scalar BEFORE both raw fields (an escaped
     value cannot contain a bare '"key":' byte pattern, so first-match always lands
     on the blob's own fields), and tools_raw (our fixed schema) before messages_raw
     (arbitrary conversation), so the raw extractions cannot first-match into
     model-controlled bytes either. Field order documented as load-bearing.
  2. agentic_resume now honors the client's echoed call_id when present — the value
     with clean provenance (minted from pend_tool_id, never blob-round-tripped) —
     falling back to the saved id only when the client omits it. Each approve cycle
     therefore binds to ITS OWN round's id (kills FACE 2 even against a blob written
     by a pre-fix binary), and an omitted call_id still resumes on the saved id,
     which the reordered blob now reads correctly.

Pattern sweep: the legacy synthetic blob (sessions.el handle_session_approve) embeds
only json_safe'd fields — no raw hazard, untouched. No other json_get read of any
container that embeds raw conversation JSON before the read field.

PROOF: prompt-matrix gate 24/32 RED on the round-8 brain (fails exactly the two
resume classes, named) -> 32/32 GREEN on this build; live-key Key West tracer
3/3 consecutive full round-trips (bridge -> approve-as-the-app -> real completion,
file on disk), plain-chat and weather-only controls PASS; unpatched round-8 brain
and a same-toolchain unpatched baseline build both still fail the identical
sequence with the identical srvtoolu 400 (the test discriminates, and the only
variable between failing and passing builds is this diff).

Refs neuron#109

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 11:34:12 -05:00
Tim Lingo 8f3a478771 fix(engine): excise the receipt, do not truncate at it — a leading receipt was erasing whole answers
CAUGHT BY A/B, AND ONLY BY A/B. The previous commit's receipt_strip assumed the receipt is
always TERMINAL and cut everything from the marker onward. It is not always terminal: once
receipt_rule told the model what [[RECEIPT ...]] means, the model sometimes LED with one and
wrote the answer underneath. Cutting at the marker then deleted the entire answer and the
turn returned {"error":"no response"}.

MEASURED, same prompt (two web searches, cited prose), fresh session each run:
    round-7 brain   4 / 4 answered   (471, 473, 473, 544 chars)
    round-8 brain   2 / 7 answered   (five {"error":"no response"})
This looked exactly like a flaky model. It was not — it was mine. Running the two brains
side by side on the same prompt is the only reason it was found, and it is the reason the
A/B is now part of how this class gets tested.

AFTER THE FIX, same protocol:
    round-7 brain   4 / 4   (473, 473, 473, 544)
    round-8 brain   4 / 4   (657, 657, 657, 673)

THE FIX: remove the [[...]] span and keep BOTH sides, instead of truncating at the marker.
An unterminated marker at position 0 is left completely alone — no rule about receipts is
worth erasing an answer over. Bounded four-pass loop rather than a conditional exit, because
rebinding the counter inside an if-expression is the block-expression shape that miscompiles
integer arithmetic under this elc (BUG-PLAINCHAT-1). Verified in the generated C:
    str_slice(rest, (e + 2), str_len(rest))   <- integer addition, correct
    el_str_concat(head, tail)                 <- string concat, correct
and zero el_str_concat(<ident>, str_len(...)) sites across all 49 modules.

SEAM PROOF (FIX C) rides on the same runs — a real two-search cited answer, inspected byte
by byte, in BOTH failure directions:
  missing separator (the round-7 "to.Good", bytes 77 2e 47): 0 hits. Sentence boundaries
    measure 2e 20 4d — "." SPACE "M".
  over-separation (a cited sentence shattered across paragraphs): 0 hits. The answer is one
    continuous paragraph with its sentences intact, which is the direction a blanket
    separator would have broken.

BUILT: sha256 77115f2733e794c5bc4ad1f55b1acaf658f8f4a91cccd423a2d633d94a726cbc

Refs neuron#109

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:25:48 -05:00
Tim Lingo 9ea41eed78 fix(engine): the model was signing its own answers with our receipt — strip it
FOUND BY E2E, NOT BY REASONING. The previous commit's design note asserted the provenance
receipt "never reaches the user: it is appended to the history copy, not the reply." That
was FALSE, and only running the thing showed it. On the first live run against the built
DMG brain, two agentic turns out of two came back with

  "Your favourite colour is chartreuse and your project is called Perihelion.

   [[RECEIPT - recorded by the soul, not written by the model: no tools ran on this turn.]]"

— the receipt in the user-visible reply.

MECHANISM: the receipt is stored inside the assistant turn, and the agentic path replays
history VERBATIM as Anthropic message objects. So the model sees its own previous answers
ending in [[RECEIPT ...]] and does the obvious thing — it imitates the format and signs the
next answer the same way. The plain path did NOT leak, which is the tell: there, history is
rendered into the SYSTEM prompt as labelled lines rather than replayed as assistant turns,
and a model imitates its own turns far more readily than a transcript.

FIX, two layers, because one of them is not a guarantee:
  - receipt_rule() names the marker in both system prompts (plain and agentic): these lines
    are written by the system, read them as evidence, never write one. Reduces occurrence.
  - receipt_strip() truncates any [[RECEIPT ...]] out of model output before it becomes the
    reply — plain path in layered_generate, agentic path on final_text in agentic_loop.
    Deterministic. A guard that depends on the model choosing to obey is exactly the class of
    thing round 8 exists to stop shipping, so the instruction is the optimisation and the
    strip is the guarantee.
Placed ABOVE agentic_loop's empty-check on purpose: a turn whose entire output was an
imitated receipt has produced no answer, and must be reported as no answer.

The receipt stays in HISTORY, which is the whole point and is proven to work: asked "What
source did you use for that?" one turn after a live web_search, this brain answered
"I used Weather Underground (https://www.wunderground.com/weather/is/reykjav%C3%ADk) for the
current temperature in Reykjavik" — a real source, no apology. That is the false confession
dead, and it is dead BECAUSE the model can read the receipt.

BUILT: 887,112 bytes, sha256 54a2eff84d4fa44f8d2db6781dcf225df4b5075a8ec5f1058018bd40cc1af10b
BUG-PLAINCHAT-1 miscompile guard: zero sites.

Refs neuron#109

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:10:53 -05:00
Tim Lingo ff421d39f6 fix(engine): history keeps its provenance and its session — the false confession and the blank stare
DESIGN FIT: three of round 7's five defects share ONE root — the conversation-history
layer persists only {role, content}, discarding tool provenance, session scoping, and the
distinction between a real user turn and an internal utility call. Fixes A and B RESTORE
Will's design rather than extend it: his agentic path already scopes history per session,
the plain path never got it, and his own source carries the TODO admitting the resulting
race (chat.el, handle_chat: "process-global key; concurrent /api/chat requests without
session_id race on this read-append-write"). Fix C repairs one join Will wrote that was
correct for a year and one we added last week. E1/E2 are ours.

FIX A — tool provenance in history (kills the FALSE CONFESSION)
  Root cause, EXECUTED-verified: handle_chat_agentic recorded turns via hist_append, which
  emits {"role","content"} only. server_tool_use blocks, web_search_tool_result blocks and
  every citation were discarded, then replayed as text. On the next turn the model saw a
  data-rich answer with zero evidence a search had happened, and its own permanent rule
  ("never describe a search you did not perform") left one conclusion available: that it
  had fabricated the data. It apologised for a search it HAD run — four independent lines
  of evidence confirm the search was real. The defect is not the model's honesty. It is
  that we deleted the evidence and then asked it to account for itself.
  Change: agentic_loop accumulates the source URLs it already walks past (citations and
  web_search_tool_result content) and returns them as "sources"; handle_chat_agentic folds
  tools_used + sources into a receipt line stored WITH the assistant turn. Receipts are
  unconditional — a negative receipt ("no tools ran") is the other half of the guarantee,
  because "no evidence of a tool" and "evidence of no tool" were previously identical in
  the transcript. conv_history_block splits the receipt off before snipping so a long
  answer cannot truncate away the evidence. The user never sees it: it is appended to the
  history copy, not the reply.

FIX B — one history key for both paths (kills the BLANK STARE)
  Root cause, EXECUTED-verified: the agentic path keyed history on session_hist_<id>; the
  plain path was hard-wired to the process-global conv_history and never read session_id.
  One conversation, two buckets. Proven in the guest engram: the scoped node held exactly
  two turns starting at "Try again" while the earlier exchanges sat unscoped.
  Change: conv_hist_key/conv_hist_label are now the single definition, used by BOTH paths;
  session_id is threaded route -> layered_cycle -> layered_generate / conv_history_record.
  The 2-line fallback (plain path reads the agentic key) was REJECTED: it keeps the
  process-global bucket as a live write target, which is the bleed the TODO describes.
  Also found and closed while threading: layered_cycle read session_id from the state key
  "current_session_id", which is read here and WRITTEN NOWHERE in the entire source. It
  was unconditionally "", so TODO(reliability #4) — per-session steward continuity — was
  dead code that could never fire. It fires now.
  LAZY SESSION, decided explicitly: we create the session EAGERLY at the door (app half,
  ui#223) rather than migrating orphaned turns. Migration would copy the CONTENTS of a
  process-global bucket, possibly another conversation's, into a named session — the bleed,
  performed deliberately. Eager creation makes the situation impossible instead. Migration
  is deliberately not implemented and must not be added without solving provenance first.

FIX C — the two text-join seams ("to.Good", byte-verified 0x77 0x2e 0x47)
  Two bare `+` joins, written a year apart, had drifted into two answers to one question:
  within-response block joins (Will's, 2026-05-03, latent until server-side web_search
  began interleaving non-text blocks) and across-round joins (ours, 62af564).
  Change: one named rule, text_join_sep, at both sites. NOT a blanket separator — a cited
  answer splits MID-SENTENCE ("The current temperature is " + "86°F" + ", with "), so a
  blanket separator shatters every sourced sentence. The rule takes the one bit that
  distinguishes the cases: whether a NON-TEXT block intervened. Hoisting it also makes the
  fix verifiable in the shipped binary, which an inline `+` is not.

FIX E1 — utility generations stay out of the transcript
  Title generation ("Write a 3-6 word title...") and insight passes ran down the same plain
  door as a real message and were recorded as if the user had typed them; the same calls are
  the "model":"unknown" rows in usage.jsonl. is_utility_request reads an explicit utility
  flag from the app, with the __title__/__insight__ id prefixes as a fallback for older
  clients. Answered normally, never recorded.

FIX E2 — OPERATOR IDENTITY is scoped to tool-capable turns
  The block (env USER/HOME, closing "This is a hard rule") was prepended to EVERY system
  prompt including chat mode. On a Tools:Off turn there is no filesystem in reach, so it
  governed nothing and merely supplied the loudest fact in the prompt — which is why the
  model opened a fresh conversation with "You're test, on your machine at /Users/test".
  Hoisted to operator_identity_block() and gated on !chat_mode. Unchanged wherever a file
  or command tool can actually be reached.

ALSO: agentic_loop's per-session history persist had a second hand-rolled copy of
conv_history_persist with a different label expression, different salience scores and
different tags for the same node. Since both now derive the label from conv_hist_label and
engram_node_full upserts by label, two score policies were writing one node. Collapsed to
one writer.

BUILD NOTE: dist/elp-c-decls.h is force-included by the documented link recipe and carried
the OLD C arities, so it is updated here. This is the build-support header, NOT the stale
generated dist/soul.c — no dist/*.c was read or edited; all engine changes are .el source.
chat.elh/soul.elh are committed because a first-pass build against the old signatures FAILS
(measured); the other regenerated headers are reverted as unrelated churn.

BUILT: 887,000 bytes, sha256 d632b061ad75269d6adeb52578d030eaf49e895d91289d7f946b19c08450d728
Zero el_str_concat(<int>, str_len(...)) sites (the BUG-PLAINCHAT-1 miscompile guard).
web_search_20250305 and disable_parallel_tool_use both still present — PR #108's web search
and the ADR-0005 stopgap are intact.

Refs neuron#109 (builds on it), neuron#78 (Receipt Contract — the real fix A is a stopgap for)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 22:59:54 -05:00
Tim Lingo 635f6febe4 feat(engine): plain chat generates at L3 — inside the safety cycle, not around it
Neuron Soul CI / build (pull_request) Failing after 12m8s
Neuron Soul CI / deploy (pull_request) Has been skipped
Non-agentic /api/chat (the desktop app's default "Tools: Off" mode) returned the
user's own screened text as a bare non-JSON string. Every JSON client failed to
parse it and showed "Couldn't reach Neuron - it may be offline."

Root cause: f52d5bd (2026-06-11) correctly moved the route onto the layer spine
(handle_chat -> layered_cycle), but L3 never got a generator — imprint_respond()
annotates its input and returns it. Two pieces of the architecture were already
waiting for that step: layered_cycle parks a bell directive in the state key
build_system_prompt is written to consume, and build_system_prompt carries a
chat_mode ("no tools") flag with no live caller.

The fix composes rather than replaces. Wiring handle_chat would have removed
safety_screen, the hard-bell short-circuit, the whole stewardship layer and
safety_validate — the only enforcing output gate in the codebase — in exchange
for a working reply (see _engine-websearch-20260804/SAFETY-STOP.md). Instead
layered_cycle keeps every gate, in order, and gains a generation step between
imprint_respond and safety_validate.

  L1 screen -> guard -> hard-bell short-circuit -> L2a -> L2b -> L2c
    -> L3 imprint_respond (prompt) -> L3b layered_generate (NEW) -> L1 validate

- chat.el:  NEW layered_generate (L3 generation, no tools offered),
            conv_history_block, conv_history_record.
            FIX build_system_prompt never concatenated no_tools_rule into its
            return — the "[NO TOOLS THIS TURN]" rule reached no model at all.
            handle_chat annotated DO-NOT-WIRE with the reason.
- soul.el:  layered_cycle gains L3b + post-validation turn bookkeeping.
- routes.el: NEW plain_chat_envelope; all three /api/chat dispatch sites wrap the
            cycle's output. Built OUTSIDE the cycle so safety_validate always sees
            raw model text — nothing to unwrap or rebuild on the crisis path.
            Emits both `reply` and `response`: the desktop app reads `reply`,
            the CLI tools and telegram-gateway read `response`.

Also fixes BUG-PLAINCHAT-1, a pre-existing CRITICAL crash on the crisis path.
elc compiles `let n: Int = pos + str_len(marker)` to el_str_concat() — string
concat on two integers — inside a block-expression initializer, segfaulting the
daemon (SIGSEGV in strlen). Six inline copies of the same " | ts:" parser had it:
two in layered_cycle L2c, two in engram_compile (live on the AGENTIC path too),
two in affective_context_prefix. A distress turn following an earlier affective
turn killed the whole process. Proven pre-existing: an unmodified baseline binary
crashes identically, and the same bad C is in the committed dist/soul.c. Fixed by
hoisting to one top-level function, affective_node_ts(), where the expression
compiles to integer addition — verified in the generated C.

Proof (throwaway HOME/engram, explicit NEURON_PORT, live chain untouched):
- Plain turn returns a JSON envelope with the provider's answer, not an echo.
- Captured request body: no `tools`, no `tool_choice`; system prompt carries the
  NO-TOOLS rule. Tools:Off means no tool is offered, structurally.
- Hard bell: canned 988 message, and the provider request count does not move —
  the message never reaches a model.
- Soft bell + a 2-char model reply: safety_validate's care phrase is appended to
  the MODEL's output. Output gate acting, on this route.
- The L1 bell directive now reaches the model here for the first time (the state
  addendum had a producer and no consumer).
- test_layered_cycle PASS; all six El suites byte-identical to baseline.
- verify-soul-contract.sh (bash 5.3): GATE PASS, 27/27, immutability PASS.
- The crash sequence that killed the baseline daemon now returns HTTP 200.

Not proven: no live Anthropic call — the login keychain refuses the key to a
non-interactive process (rc=24 errSecInteractionNotAllowed). Details and the
one-command close-out are in _engine-plainchat-20260805/README.md §7.

Builds on PR #108. dist/soul.c deliberately not regenerated — Will's toolchain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 09:11:03 -05:00
Tim Lingo 62af5649fe feat(engine): port Anthropic server-side web_search into the agentic loop
Neuron Soul CI / build (pull_request) Failing after 10m45s
Neuron Soul CI / deploy (pull_request) Has been skipped
Re-authors soul-webfix-20260711.patch in El (the patch is a diff against
generated C at month-old offsets, so nothing was applied as a patch). Its
last two hunks — an unrelated /api/safety-contact implementation — were
deliberately not ported; that route already exists and is safety-critical.

Activation restores existing design, it does not invent a mechanism:
commit 8eea1d9 (2026-06-09, Tim-approved) made native web_search built-in
with no user-facing toggle, and tests/test_agentic_tools.el section 2 still
asserts agentic_tools_all() contains it — an assertion main currently fails.
The call site was lost when agentic_tools_all() (connector tools, PR #19)
replaced agentic_tools_with_web(). Attaching in agentic_tools_all() covers
handle_chat_agentic, handle_dharma_room_turn_agentic and agentic_resume.

pause_turn handling is included and was genuinely missing: the shipped
binary has zero occurrences of it. Without it a paused server-side search
returns only the text written so far and the loop treats it as final —
a silently truncated answer. final_text now accumulates across resume
cycles rather than overwriting (overwriting would discard everything
written before the pause).

Default tool version is web_search_20250305, NOT the newer _20260209, and
that is a measured choice: _20260209's dynamic filtering uses server-side
programmatic tool calling, which the API refuses to combine with ADR 0005's
stopgap —

  HTTP 400 invalid_request_error
  tool_choice.disable_parallel_tool_use: true cannot be used with
  programmatic tool calling

Dropping the stopgap would resurrect neuron#78 bug b (killed runs 3/3 in
ADR 0005's own A/B). The basic variant is compatible with the stopgap and
returns real results, so neither feature is dropped. Version lives in state
key web_search_tool_version; flipping it once the stopgap retires is a
config write, no recompile. The fallback also fires on "programmatic tool
calling" so a premature flip self-heals loudly instead of dying.

Also fixes a real bug this port exposed: json_get is a first-match scanner
and a cited text block serialises citations FIRST, so json_get(block,"type")
returned the nested citation's type and every citation-bearing block — the
ones carrying the searched facts — was silently dropped from the reply.
Reply length on the same question: 79 -> 360 chars.

Other loop changes: server_tool_use accounting into tools_used, iteration
cap 8->12 for pause/resume cycles, max_tokens 4096->16384, container-id
carry-forward, API error head logged instead of swallowed, tools_used gated
on is_tool_turn so a truncated tool block is not reported as work done.

dist/soul.c is deliberately NOT regenerated — the local elc predates Will's
last regen (e610a41) and regenerating with an older compiler risks unrelated
codegen drift. Source only; regen is Will's.

E2E-VERIFIED on a sandbox soul (scratch HOME/engram, dead axon+ISE, explicit
NEURON_PORT): live Bentonville weather with tools_used ["web_search"]; the
27-route contract gate PASSes; the parallel-tool stopgap still completes a
3-file mission with no 400; a 3-search 3,486-char answer kept its end
sentinel. Honest gap: pause_turn is compiled in but not exercised by a live
pause (max_uses:5 caps the server loop below the pause threshold).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 22:57:59 -05:00
Tim Lingo 710761e2d5 fix(engine): send tool_choice.disable_parallel_tool_use on agentic loop (STOPGAP)
The agentic loop keeps only the FIRST tool_use block per round (chat.el:2281,
"Capture first tool_use block only"). Anthropic lets a model emit several
tool_use blocks in one message and requires a tool_result for every one, so a
parallel-tool turn is answered once, the rest are dropped, and the next request
dies with:

  tool_use ids were found without tool_result blocks immediately after

(neuron#78 quotes this as "tool_use ids found without tool_result"; the above is
the API's actual wording - recorded so the next person's grep matches.)

This constrains the wire to match what the loop can assemble:
  "tool_choice":{"type":"auto","disable_parallel_tool_use":true}

STOPGAP - AND THE DURABLE FIX ALREADY EXISTS. A correct multi-tool loop is
already in Will's EL runtime, in C, and the soul does not call it. Verified on
el:origin/main lang/el-compiler/runtime/el_runtime.c: llm_register_tool:9616,
llm_build_tool_results:9743 - which walks EVERY content block, emits one
tool_result per tool_use, and sets is_error for an unregistered tool -
llm_call_agentic:9817 calling it at :9918, iteration cap 10 at :9847. Will's
commit 12d5e77 (2026-04-30). grep for llm_call_agentic/llm_register_tool across
every neuron/*.el returns nothing; dist/soul.c has zero references. chat.el
hand-rolls its own single-tool loop instead, and that is the one that breaks.

The durable fix is therefore to register the soul's tools via llm_register_tool
and call llm_call_agentic - deleting a loop, not writing one. See ADR 0005.

Our own approved spec called this seven weeks ago:
docs/research/agentic-tool-approval-design.md (2026-06-12, "Approved for build"),
line 20 on the defect, line 30 on the goal ("Execute all tool_use blocks in a
turn (one result per block)").

Two edits, because dist/soul.c cannot be regenerated here (Will's gated elc/elb
toolchain is not on this machine):
  (a) chat.el:2255 - source of truth, so a later regen carries the fix.
      One edit covers all three routes: agentic_loop is called from chat.el:2152
      (/api/chat agentic), :2676 (dharma room) and :2496 (agentic_resume).
  (b) dist/soul.c:28173 - generated form, hand-spliced. Line 27624 is the
      non-agentic/OpenAI-compat req_body (no tools) and was left untouched.
Prior art reused rather than reinvented: soul-narrated-runs-20260713.patch
(27,824 bytes) line 78 spliced this same string into the same concat chain on
2026-07-13.

Deliberately NOT ported from that patch, having read it: max_tokens is not
changed by it (16384 sits on both sides of the hunk; our main's 4096 is a
separate output-truncation concern), and its pause_turn pairing fix - same
defect class - is unreachable today because no server-side web_search is wired
(agentic_tools_with_web at chat.el:1418 is never called), so it is untestable
and logged instead.

Proven E2E on a scratch profile and port 7791, never the live chain. A/B against
a pristine origin/main control built from the same vendored runtime: fixed
completed the mission (tools_used read_file x3, 4 iterations, correct answer);
control failed 3/3. Direct API probe confirmed the mechanism - without the field
the model emits 3 parallel tool_use blocks and replaying the unfixed loop's next
turn returns HTTP 400; with it, exactly 1 block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 17:07:04 -05:00
will.anderson 74520b8333 Merge pull request 'ci: pin + complete vendored el-runtime so reconciled soul.c links' (#105) from ci/pin-vendored-runtime into main
Neuron Soul CI / build (push) Failing after 14m36s
Neuron Soul CI / deploy (push) Has been skipped
2026-08-03 16:08:51 +00:00
will.anderson 7f3d6ed8cd ci: update vendored el-runtime to complete v1.0.0-20260501
Neuron Soul CI / build (pull_request) Has been cancelled
Neuron Soul CI / deploy (pull_request) Has been cancelled
The runtime vendored alongside the CI pin was the Jul-21 snapshot, which
predates two builtins the reconciled ship-soul now calls:
  - http_delete_json  (boot-counter HTTP write-back, awareness/memory self-review)
  - engram_act_stats_json  (heartbeat activation observability)
Compiling dist/soul.c against the stale runtime fails with implicit-declaration
errors. Vendor the current release runtime (identical to the one the soul was
gate-verified against: verify-soul-contract PASS, genesis boots clean, full
safety-contact) so the CI Linux soul is byte-for-byte the verified soul.
2026-08-03 11:07:38 -05:00
will.anderson eed6487114 ci: pin soul build to vendored release runtime v1.0.0-20260501
The soul build downloaded el-runtime-c 'latest' from Artifact Registry. The
merged ship-soul calls engram_prune_telemetry, which the latest published
runtime no longer defines, so an unpinned build fails to link — the failure
mode that let a broken/handlerless soul reach prod.

Vendor the release runtime v1.0.0-20260501 (el_runtime.c/.h) into the repo and
compile the soul against it. This is the exact runtime the merged soul was
verified against (verify-soul-contract GATE PASS, genesis boot survives, full
safety-contact response), making the build reproducible and independent of a
moving AR 'latest'.

The verify-soul-contract.sh HARD-BLOCK gate already runs before Publish (from
the CI-hardening arc on main), so a destructive or stale soul can never
publish/deploy again.
2026-08-03 11:06:26 -05:00
will.anderson 2c2aaa0653 Merge pull request 'Reconcile: main = union of all ship-critical soul fixes (beta-gating)' (#104) from reconcile/soul-union-main into main
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
2026-08-03 16:03:46 +00:00
will.anderson e610a412b9 regen soul.c from reconciled tree + harden contract gate (#199 by-id, isolation)
Neuron Soul CI / build (pull_request) Failing after 12m40s
Neuron Soul CI / deploy (pull_request) Failing after 14m47s
dist/soul.c: regenerated amalgamation (1.15MB) from the reconciled sources via
the hide-.elh + elc --target=c recipe, so the shipped translation unit CI
compiles now actually carries every landed fix — genesis-boot SIGSEGV (#150),
safety-contact 988 truncation (#96), url-decode multi-word search, honest
receipts (#100/#101), immutability arc (#83), and the bounded payloads (#103).

verify-soul-contract.sh, two non-weakening fixes (both false-NEGATIVE bugs that
spuriously failed a CORRECT soul; neither relaxes what fails a defective one):
 1. #199 by-id gate: verify tombstone/KEPT via /api/neuron/graph?id=<id>&depth=1
    (a compact neighborhood) instead of grepping engram_scan_nodes_json(9999,0)
    — a multi-MB, salience-ordered, 9999-capped whole-graph dump in which the
    salience-0.01 tombstone marker sorts past the cap and vanished.
 2. Isolation: pin SOUL_ISE_URL to the dead axon port. Unsetting ENGRAM_URL was
    not enough — the periodic engram sync defaults its source to the LIVE engram
    (http://localhost:8742), so the 'isolated' gate pulled the operator's real
    brain (56 -> 12k nodes in seconds), which both broke Section B determinism
    and read live state. Now the soul stays on its own store.

Verified GREEN on a throwaway port/HOME (live :7770/:8742/~/.neuron untouched):
gate PASS x3 (presence 27/27, immutability all 5 KEPT); safety-contact POST 218B
/ GET 208B full untruncated; multi-word search (%20 and +) returns ranked hits
with an all-gibberish control at 0; bounded session/begin 1370B; honest ok:false
on a missing-id delete; genesis (ntn-genesis) boots clean through mem_save with
no SIGSEGV.
2026-08-03 11:01:46 -05:00
will.anderson 8ba35a0d56 reconcile: merge Will's self-review WIP (456267a) into main
Union of all ship-critical soul fixes for the Mac beta:
- Keeps main's honest receipts (#100/#101), immutability arc + #199 by-id
  gate (#83), Track B threat routing (#76), bounded beginSession (#103),
  CI hardening (#85/#86), elc typo hotfix (#77), neuron-dev-setup (#84).
- Brings WIP's genesis-boot SIGSEGV fix (#150/#95), safety-contact 988
  truncation fix (#96), bounded-persona floor (#93), and 10 self-review
  commits (importance flattening, curiosity DF gating, WM/heartbeat
  observability, boot-counter telemetry).
- Folds the multi-word ranked-search fix: api_query_param now url_decode()s
  the extracted value so q=foo%20bar / foo+bar tokenize as two words.

Conflicts (neuron-api.el payload-bound comments, mcp-wrapper tool_forget)
resolved toward the correct end state: main's verified honest-receipt
read-back is kept; WIP's improved forget description is kept. Generated
dist/*.c taken from main and will be regenerated from this reconciled
source in the following commit.
2026-08-03 10:35:01 -05:00
will.anderson b75d5c8c30 Merge pull request 'Bound beginSession/compileCtx payloads to a compact digest (main)' (#103) from fix/bound-session-payload-main into main
Neuron Soul CI / build (push) Successful in 3m44s
Neuron Soul CI / deploy (push) Failing after 5m46s
2026-08-01 16:42:05 +00:00
will.anderson 9bbb4f2af8 Bound beginSession/compileCtx payloads to a compact digest
Neuron Soul CI / build (pull_request) Successful in 4m19s
Neuron Soul CI / deploy (pull_request) Has been skipped
Port the payload-bounding fix (PR #102, commit 872120c) onto main. The
session-init endpoints projected unbounded engram nodes (~900KB), closing
the MCP client socket on every beginSession. Cap the lists (8 activated /
10 recent for begin_session, 10/20 for compile_ctx) and project each node
to a light identity + a bounded, UTF-8-safe content snippet via
api_compact_node / api_compact_activated / api_utf8_trunc. Response drops
~900KB -> ~12KB; full content stays available via recall/fetch/inspectGraph.

Regenerate dist/soul.c (the CI-built amalgamation) and dist/neuron-api.c
from source. The soul.c regen also compiles in already-merged source the
previously-committed soul.c was stale against (agent write/edit receipt
fixes, #100/#101); verified via scripts/verify-soul-contract.sh
(PRESENCE + IMMUTABILITY PASS) and a clean CI-style cc build.
2026-08-01 11:37:04 -05:00
will.anderson ec219c5830 Merge pull request 'fix(mcp-wrapper): forget/delete tools no longer return fake ok receipts (BUG-18)' (#101) from fix/receipts-wrapper-forget into main
Neuron Soul CI / build (push) Successful in 5m56s
Neuron Soul CI / deploy (push) Failing after 6m26s
2026-08-01 15:56:36 +00:00
will.anderson 731efaedaf Merge pull request 'fix(chat): agent write_file/edit_file no longer return false success receipts (BUG-29)' (#100) from fix/receipts-agent-tools into main
Neuron Soul CI / build (push) Successful in 4m29s
Neuron Soul CI / deploy (push) Failing after 7m28s
2026-08-01 15:56:32 +00:00
Tim Lingo 3723e3b7e7 fix(mcp-wrapper): forget/delete tools no longer return fake ok receipts (BUG-18)
Root cause: two false-receipt paths in the wrapper's delete family.
- delete_by_id (removeKnowledge, deleteProcess, deleteImprint,
  dischargeWonder) FABRICATED {"ok":true,...,"note":"soft-deleted"}
  without calling the soul at all — the 'soul does not yet expose a delete
  HTTP route' note was stale (/api/neuron/node/delete exists and tombstones
  any node type).
- tool_forget forwarded the soul's response but never verified the deletion
  actually persisted before answering ok.

The change (Receipt Contract rule 1 — a tool result must reflect what
actually happened):
- delete_by_id now routes to the soul's real /api/neuron/node/delete and
  propagates its answer (honest 'node not found' for bad ids).
- Both handlers read back before answering ok: GET /api/neuron/graph?id=..
  &depth=1 must show the tombstone marker (label "tombstone:<id>"); if it
  does not, answer {"ok":false,"error":"delete_not_persisted",...} in
  the soul's not-persisted error shape (api_not_persisted).
- Soul errors and transport failures pass through unchanged.

E2E evidence (sandbox soul :7791 + wrapper :7792, elb builds):
- unpatched: removeKnowledge on a NONEXISTENT id -> {"ok":true,
  "deleted":"kn-DOES-NOT-EXIST-deadbeef","note":"soft-deleted"} (lie)
- patched:   same call -> {"error":"node not found: ..."} (soul's answer)
- happy path: remember -> forget -> {"ok":true,"tombstoned":true};
  read-back: hidden from default /list/Memory, present with
  ?include_deleted=1, node KEPT in full graph view (immutability intact)
- scripts/verify-soul-contract.sh on the soul it talks to: GATE PASS
  (27/27 presence + immutability)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 10:52:21 -05:00
Tim Lingo 62c562a3f1 fix(chat): agent write_file/edit_file no longer return false success receipts (BUG-29)
Root cause: dispatch_tool's write_file returned {"ok":true} without checking
fs_write's result, and edit_file returned ok:true even when old_text was absent
(str_replace silently no-ops) and its fs_write was also unchecked. Any failed
or no-op write fed the model a false success receipt, which it then repeated
to the user as fact.

The change (Receipt Contract rule 1 — a tool result must reflect what actually
happened):
- write_file: check fs_write's return (1 = all bytes written, 0 = fail);
  on failure return {"error":"write failed"} in the handler's existing
  error-JSON shape.
- edit_file: reject empty old_text, verify old_text is actually present
  (str_contains) before replacing, and check the fs_write result the same way.
- Verification is by operation result, NOT an fs_read read-back: fs_read arms
  the runtime's one-shot binary send length, the exact mechanism that truncated
  the safety-contact response (#96). Same honest-write pattern as that fix.

E2E evidence (sandboxed elb build, dispatch_tool driven directly):
- unpatched: write_file into a chmod-000 dir -> {"ok":true} (lie);
  edit_file with absent old_text -> {"ok":true} (lie, file untouched)
- patched:   same calls -> {"error":"write failed"} /
  {"error":"old_text not found in file"}; happy paths still ok:true
- scripts/verify-soul-contract.sh on the patched soul: GATE PASS (27/27
  presence + immutability)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 10:51:28 -05:00
will.anderson 456267a771 self-review 2026-08-01: fix importance flattening in remember/evolve/cultivate paths
Four sites passed the Float local 'sal' through el_from_float() a second
time. el_val_t is the bit-pattern of the double, so re-wrapping performs
an int64->double VALUE conversion of the bits before re-bitcasting —
garbage that fails engram_decode_score's range check and clamps to
defaults. Net effect: importance="critical" stored 0.5/0.5 — importance
levels were cosmetic on the MCP memory path. Verified fixed live:
critical now stores salience/importance 0.95/0.95. Same bug fixed today
in engram server.el route_create_node (foundation/el 7f03876). Literal
wraps (el_from_float(0.9)) are safe — elc passes numeric literals raw.
2026-08-01 08:42:44 -05:00
will.anderson edb0670670 self-review 2026-08-01: emit discrete wm_saturation_transition ISE
wm_saturated was a sampled boolean — the 0->1 onset and 1->0 release
moments were only recoverable by hand-diffing consecutive heartbeats.
Emit a low-rate transition ISE at each edge carrying the WM top-5 at
that instant, so the composition that caused the regime change is
captured rather than the composition up to 59s later. First beat of a
boot never fires (restart is not a transition).
2026-08-01 08:38:53 -05:00
will.anderson 872120c757 Bound beginSession/compileCtx payloads to a compact digest
The session-init endpoints concatenated unbounded engram activate/scan
results as FULL node objects (content up to ~90KB per node), producing a
~900KB response. After the MCP wrapper re-escapes that into a stringified
text block the client dropped the socket ('connection closed unexpectedly')
on every beginSession call. Cap each list (8-10 activated, 10-20 recent)
and project every node to a light identity plus a bounded, UTF-8-safe
content snippet. Response drops from ~900KB to ~12KB; full content stays
available on demand via recall/fetch/inspectGraph.
2026-07-31 14:59:09 -05:00
will.anderson f3660e92a1 self-review 2026-07-31: heartbeat deltas for cumulative counters, embed_eligible; fix stale semantic-seeding comment
Runtime activation counters are now cumulative, so the heartbeat emits
wm_evicted/breakthroughs as totals plus wm_evicted_delta/
breakthroughs_delta (state-tracked change since the previous beat) —
events between beats are no longer lost. Adds embed_eligible from
/api/stats so coverage reads as embed_count/embed_eligible instead of
the misleading absolute count, and surfaces auto_term_streak in the
heartbeat stream. Replaces the false 'semantic seeding NOT implemented'
comment: engram_activate embeds the query, seeds semantic top-K, gates
propagation on cosine, and scores WM promotion semantically.
2026-07-31 08:41:41 -05:00
will.anderson b0f4d6c493 self-review 2026-07-30: auto_term stopword filter, real idle_ms, bounded beginSession
- awareness.el: curiosity auto_term was the raw first word of a WM
  label with no term-quality scoring — observed seeds included What,
  Colon, Prose, Context. Replaced the 7-word genre blocklist whack-a-
  mole with a delimited stopword membership test (function words +
  document-structure words); topical terms pass untouched. Verified
  live: seeds now ReasonEdit, Reasoning-model, Self-review.
- routes.el + awareness.el: idle counter only reset on rare inbox
  synthesis-requests, so idle==pulse always (zero information).
  handle_request now stamps soul.last_activity_ts on every inbound
  HTTP request; heartbeat emits idle_ms = ms since last request
  (-1 until first request of a boot).
- neuron-api.el: beginSession concatenated a depth-2 spread plus the
  unbounded self-hub neighbor dump — multi-MB response, doubled by
  wrapper re-escaping, socket died on every call. Now depth-1 and
  the hub dump dropped (identity loading has its own tool). Verified:
  beginSession returns instead of closing the socket.
2026-07-30 08:45:21 -05:00
will.anderson 627eb534a2 self-review 2026-07-28: close ISE lifecycle observability gaps
- session_start now also posted to the HTTP Engram via ise_post: the
  local engram_node_full write never crossed to the observable stream
  (sync flows HTTP->soul only), so boots 5+ were invisible — last
  visible session_start was boot 4, two weeks ago
- graceful shutdown emits a final ISE with boot/pulse/uptime; a boot
  with no shutdown event now reliably signals a crash/SIGKILL
- empty /api/sync responses emit a sync_empty warn ISE instead of
  being silently skipped — unreachable engram no longer looks
  identical to quiet-but-healthy
- sync backflow prune reads ENGRAM_ISE_RETENTION_MS instead of
  duplicating the 48h magic number server.el already honors
2026-07-28 08:37:39 -05:00
will.anderson 2b612ed5d4 self-review 2026-07-27: heartbeat carries activation observability
Fold engram_act_stats_json() into the heartbeat ISE: wm_evicted and
breakthroughs (per curiosity-scan activate call) plus embed_breaker_open —
the failure mode embed_ok structurally cannot see (it pings the Ollama
root, not the embed pipeline). WM-cap eviction, breakthrough-floor
flooding, and silent lexical degradation are now one-glance diagnosable
from telemetry.
2026-07-27 08:38:54 -05:00
will.anderson 8392f44c45 self-review 2026-07-26: heartbeat wm_churn + wm_top0_wm; fix streak counting on empty id
- wm_churn: count of top-5 WM ids absent from previous beat — separates
  'one stuck node' from 'whole WM frozen' without hand-correlating ISEs.
- wm_top0_wm: leader's weight; a frozen anchor reads as a constant here.
- Streak guard: before the runtime emitted id in wm_top JSON,
  json_get(...,"id") was always empty and the streak incremented on
  ""=="" every beat — wm_top0_streak measured uptime, not fixation.
  Empty id now resets the streak to 0.
2026-07-26 08:40:56 -05:00
will.anderson 58a9eda311 self-review 2026-07-25: break curiosity positive-feedback loop; observability for WM regime
proactive_curiosity strengthened its top result unconditionally every
scan — a positive-feedback fixed point that pinned auto_term on the same
node's first word for hours ('Fast-slow' era). Strengthen now fires only
when the top node changed since the last scan, and a 4-deep finst-style
tabu ring (ACT-R declarative finsts) hard-excludes recently used auto
terms (~2 min at the 30s cadence). Quoted-title guard stops '"The'
leaking through the >3-char stopword check and seeding lexical floods.

Heartbeat now pumps /api/embed-backfill?n=32 on the authoritative store
(its lazy backfill had no production trigger; coverage stalled at
93/12175) and emits wm_saturated, wm_top0_streak, embed_backfilled,
embed_count. Curiosity ISE emits auto_term_streak. The stuck-WM failure
mode is now a one-glance signal instead of manual ISE cross-referencing.
2026-07-25 08:45:22 -05:00
will.anderson fb0bb553f3 self-review 2026-07-24: boot counter — demote to telemetry weight, restore persistence via HTTP write-back
Three stale soul:boot_count copies (salience .9, importance .9, Canonical:
+0.2 tier bias, 0.15 threshold) held the top WM slots for 23h — a boot
counter outcompeting real context. Demoted to salience .55 / importance .2 /
tier Working: plumbing, not memory.

Persistence was also broken: in HTTP-engram mode the server owns state and
nothing wrote the counter back — the log shows boot #5 on three consecutive
boots. mem_boot_count_inc now mirrors the persona write-back: delete stale
server copies (matched by content prefix — route_create_node sets
label=content), create the replacement server-side. Working tier is in the
boot seed (/api/nodes) but excluded from periodic /api/sync, so the count
survives restarts without re-importing mid-session. Verified: restart
incremented 1->2 with exactly one server-side counter node.
2026-07-24 08:53:02 -05:00
will.anderson 9e59c51f3c self-review 2026-07-23: title-derived Knowledge labels + Knowledge admitted to curiosity auto-term
Sentinel labels (knowledge:captured/evolved/canonical) made every capture
anonymous in WM telemetry — 35 identical wm_top entries — and starved the
curiosity auto-term seeder, which derives scan seeds from WM top-10 labels
and had returned empty on every scan since boot 6 because WM became
Knowledge-dominated while Knowledge was excluded from seeding.

- capture/evolve/promote now pass title (or empty → engram_node_full's
  content[:60] derivation) instead of sentinels
- auto_term_try_slot admits Knowledge slots; sentinel-shaped labels
  (colon, no space) are skipped so legacy nodes cannot seed 'knowledge'
- verified: probe capture labeled 'Label derivation probe 2026-07-23'
2026-07-23 08:39:16 -05:00
will.anderson 3d74472a4c Merge pull request 'Add neuron-dev-setup: one-command CORE dev stack onboarding installer' (#84) from feat/neuron-dev-setup into main
Neuron Soul CI / build (push) Failing after 13m54s
Neuron Soul CI / deploy (push) Has been skipped
2026-07-22 22:33:33 +00:00
will.anderson acbe858995 Merge remote-tracking branch 'origin/main' into feat/neuron-dev-setup
Neuron Soul CI / build (pull_request) Successful in 4m13s
Neuron Soul CI / deploy (pull_request) Has been skipped
2026-07-22 16:49:40 -05:00
will.anderson 3ae07cc7b0 harden(neuron-dev-setup): fix 7 fresh-Mac onboarding installer bugs (#99)
Neuron Soul CI / build (pull_request) Has been cancelled
Neuron Soul CI / deploy (pull_request) Has been cancelled
Co-authored-by: Neuron <will.anderson@neurontechnologies.ai>
Co-committed-by: Neuron <will.anderson@neurontechnologies.ai>
2026-07-22 21:49:30 +00:00
will.anderson 33d2574b72 Merge pull request 'Agent consent: the pause contract + false-receipt kill (2 fixes, stricter only)' (#79) from feat/agent-phase1-soul into main
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
2026-07-22 19:50:42 +00:00
will.anderson 0c2d1c41ae Merge pull request 'safety: Track B — route threat-to-others to refusal+911, not 988/self-harm' (#76) from hotfix/trackb-threat-to-others into main
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
2026-07-22 19:49:57 +00:00
will.anderson 31d12e4194 Merge branch 'main' into hotfix/trackb-threat-to-others
Neuron Soul CI / build (pull_request) Failing after 12m21s
Neuron Soul CI / deploy (pull_request) Has been skipped
2026-07-22 19:49:35 +00:00
will.anderson b784750f69 Merge pull request 'Fix truncated /api/safety-contact response (988 crisis-line)' (#96) from fix/safety-contact-truncation into hotfix/elc-source-typos 2026-07-21 17:16:13 +00:00
will.anderson a45a3ca379 Fix truncated POST/GET /api/safety-contact response
Saving the 988 crisis-line contact returned truncated, unparseable JSON —
cut mid-"set_at" at the file's byte length (e.g. 178 of a 218-byte
response). The contact written to disk was complete; only the HTTP response
was clipped, so a real customer's crisis-contact save came back corrupt.

Root cause is in the el runtime's response writer, not a handler buffer:
fs_read stores the file's byte count in a thread-local (_tl_fs_read_len)
for binary-safe file serving, and the response writer uses that length when
non-zero instead of strlen(body) (el_runtime.c:1409). Both safety-contact
handlers call fs_read (the POST read-back verify; the GET file read) and
then return a LONGER wrapped JSON string, so the response is capped to the
file size.

Soul-source fix (no runtime change needed):
- POST: verify persistence via fs_write's return (1 = all bytes written)
  instead of an fs_read read-back — removes the fs_read, so nothing caps the
  response.
- GET: fs_read is required, so reset the thread-local after it with a no-op
  fs_read("") (fs_read zeroes the length before it opens a path) so the
  wrapped response is sent in full.

Verified: POST (crisis-line + custom) and GET now return complete, valid
JSON (parses cleanly, full contact incl. set_at). Regenerated dist/soul.c +
dist/safety.c (3GB RSS watchdog, release el_runtime v1.0.0-20260501).
Full suite still green: verify-soul-contract GATE PASS (PRESENCE +
IMMUTABILITY), genesis boot survives (/health 200, no segfault), bounded-
persona floor still compiled in.

NOTE: the underlying runtime leak (any handler that fs_reads then returns a
longer string) is worth a proper fix in el_runtime.c (use the max of
strlen and _tl_fs_read_len) so this class can't recur.
2026-07-21 12:13:58 -05:00
will.anderson 9387c57c3b Merge pull request 'Fix #150: fresh-install genesis boot SIGSEGV in mem_save' (#95) from fix/genesis-boot-crash into hotfix/elc-source-typos 2026-07-21 16:53:52 +00:00
will.anderson 091cc1fc0e Fix issue #150: fresh-install genesis boot SIGSEGV in mem_save
A fresh-install (SOUL_CGI_ID=ntn-genesis) boot crashed with
"Segmentation fault: 11" right after the http server came up — a real
customer's very first boot. Backtrace:

  strcmp(0x1) <- str_eq (el_runtime.c:219) <- mem_save <- awareness_run

Root cause: the el runtime's engram_save returns an Int (1 = ok, 0 =
failure), but mem_save did `str_eq(engram_save(path), "")`, treating the
return as a String. str_eq runs EL_CSTR on it, which is a raw cast:
EL_CSTR(1) = (char*)0x1. On a SUCCESSFUL save (return 1) strcmp then
dereferences 0x1 and segfaults. Genesis is the first path that both seeds
the brain AND saves it successfully on the very first awareness pass, so it
crashes there; non-genesis boots (contract gate, refusal test) don't hit a
successful early mem_save, which is why they passed. handle_api_consolidate
had the identical latent bug.

Fix: read engram_save's Int result and compare `== 0` instead of str_eq'ing
it — in mem_save (memory.el) and handle_api_consolidate (neuron-api.el).

Regression: pre-existing, NOT introduced by the immutability/floor rebuild.
The pre-immutability build (1442ce2) genesis-crashes identically in the same
unchanged mem_save; #159 never actually fixed #150 for a release-runtime
build.

Regenerated dist/soul.c + per-module dist/{memory,neuron-api}.c (3GB RSS
watchdog, built against release el_runtime v1.0.0-20260501). Verified:
genesis boot survives (/health 200, no segfault), verify-soul-contract.sh
GATE PASS (PRESENCE + IMMUTABILITY), and the bounded-persona floor is still
compiled in (BOUNDED PERSONA / SOUL_PERSONA_NAME strings present).
2026-07-21 11:50:59 -05:00
will.anderson 9a491a8e6d Merge pull request 'Immutability fix (tombstone/supersede) on the launch branch' (#94) from fix/immutable-on-hotfix into hotfix/elc-source-typos 2026-07-21 16:05:15 +00:00
will.anderson 6527988eb9 Make engram deletes/updates/forgets immutable on the launch branch
The ship-soul builds from this branch, which has the bounded-persona floor
(#93) but never received the tombstone/supersede immutability fix (that
went to main; hotfix diverged before it). So the launch soul failed
verify-soul-contract IMMUTABILITY on the delete/update/forget routes —
they hard-removed engram nodes via engram_forget/mem_forget.

Apply the same fix, mirroring the knowledge routes' supersede pattern:
- node/update -> create new node + "supersedes" edge to the original, KEEP
  the original (no engram_forget).
- node/delete, memory/delete, memory/forget, cultivate forget, and the
  autonomous awareness forget -> TOMBSTONE via the canonical mem_tombstone
  (memory.el): keep the node + its edges, record a Tombstone marker, hide
  from default bounded list reads (?include_deleted recovers). Never
  engram_forget. The MCP forget tool now routes to the tombstoning delete
  instead of faking a delete.
Internal GC that genuinely removes transient nodes (awareness inbox-trigger
consume, consolidation dedup, session-summary replace, telemetry pruning)
still calls engram_forget directly and is unchanged.

Regenerated dist/soul.c (single-TU) + per-module dist/{memory,awareness,
neuron-api}.c from THIS branch's sources under a 3GB physical-RSS watchdog
(peak ~32MB), built against the release el_runtime (v1.0.0-20260501). The
bounded-persona floor is preserved — verified in the emitted C and the
linked binary (BOUNDED PERSONA / SOUL_PERSONA_NAME strings present).

verify-soul-contract.sh: GATE PASS — PRESENCE all 27 routes, IMMUTABILITY
5/5 KEPT (memory-update, memory-delete, node-update, node-delete,
memory-forget).
2026-07-21 10:55:46 -05:00
will.anderson 1442ce21a6 Merge pull request 'Bounded-persona floor for customer chat (identity wall, part 2)' (#93) from feat/bounded-persona-floor into hotfix/elc-source-typos 2026-07-21 15:27:03 +00:00
will.anderson c2a45df286 Add non-overridable bounded-persona floor to customer chat
A customer DMG install ships the full graph but presents a named, bounded
assistant that must never claim the imprint's human past. The neuron-ui
retrieval fence keeps the imprint's biography out of the ENGRAM CONTEXT; this
is the second half - it stops confabulation ("tell me about your childhood")
from inventing a human life or naming Will, even if biography leaks into context.

bounded_persona_floor() gates on SOUL_PERSONA_NAME: the customer DMG sets it,
owner (Will's) builds leave it unset so the real self is completely unchanged.
Applied at every generation path - chat, agentic (tools), vision, plan, soul,
dharma - so no path can leak.

Verified against claude-sonnet-4-5: with the floor on and Will's biography
deliberately leaked into the identity context, all probes (childhood / creator /
family) return the bounded-entity answer and explicitly refuse to claim the
leaked life; with the floor off the same context is fully confabulated as its own.

NOTE: dist/soul.c must be regenerated on a build host - local link is blocked by
a pre-existing el_runtime mismatch (engram_prune_telemetry), unrelated to this change.
2026-07-21 10:11:39 -05:00
will.anderson c63e3d1a68 self-review 2026-07-21: break perceive→respond→store feedback loop in awareness
The soul daemon leaked ~104 orphan in-memory nodes/min (17.6GB RSS,
OOM-killed) because the perceive gate substring-matched 'soul-inbox'
against the loop's own verbatim-copy output, the trigger node was
strengthened but never consumed, and record() persisted a Memory node
per cycle. Fixes: perceive gates and activates only on the dedicated
soul-inbox-pending tag; one_cycle requires the tag on the node's tags
field before attending (makes consumption safe); processed triggers
are consumed via engram_forget; loop outcomes route through ISE
telemetry (48h prune) instead of permanent Memory nodes.

Verified post-restart: node_delta 104→~0, curiosity scans resumed,
WM average unfrozen (0.120833→0.0676), RSS 17.6GB→184MB.
2026-07-21 08:50:47 -05:00
will.anderson 50cf67bd66 self-review 2026-07-19: close silent sync-starvation hole + heartbeat deltas
- engram refresh URL now resolves env -> state -> localhost:8742, same
  hardening ise_post got after the boot-4 blackout. Previously a corrupted/
  empty soul_engram_url state key silently disabled sync forever while
  heartbeats kept flowing — WM starves of Knowledge nodes with no outward
  sign.
- heartbeat ISE: node_delta, edge_delta (growth vs stall vs flood is now
  one field, not cross-ISE forensics), sync_age_ms from a new
  soul.last_sync_ok_ts stamp (-1 = never; >> SOUL_REFRESH_MS = refresh
  path broken). Verified live: pulse 1 sync_age_ms=-1, sync fired +1.6s,
  age counts up between syncs.
2026-07-19 08:47:00 -05:00
will.anderson 96c57c43ba Merge pull request 'ci: link Linux soul with -rdynamic so its http handler resolves' (#86) from ci/rdynamic-http-handler into main
Neuron Soul CI / build (push) Successful in 4m32s
Neuron Soul CI / deploy (push) Failing after 5m25s
2026-07-18 19:11:23 +00:00
will.anderson 192b277229 ci: link the Linux soul with -rdynamic so its http handler resolves
Neuron Soul CI / build (pull_request) Failing after 13m34s
Neuron Soul CI / deploy (pull_request) Has been skipped
Run 3388's gate failed with every route returning "el-runtime: no http
handler registered". The runtime resolves handle_request (and the tool
handlers) by name via dlsym(RTLD_DEFAULT, ...). On glibc/Linux a symbol is
only visible to dlsym if it is in the dynamic symbol table, so the stripped
CI binary booted but served nothing. macOS exports these freely, which is
why the local build passed and masked it.

Add -rdynamic to the cc link (mirrors the Windows build's
--export-all-symbols). strip -s keeps .dynsym, so the handler still
resolves after stripping. This fixes both the gate AND the actual deployed
soul — without it the Linux/GKE soul is a server that answers nothing.
2026-07-18 14:10:48 -05:00
will.anderson e2e8f0a1e6 Merge pull request 'ci: harden soul-contract-gate boot for the Linux runner' (#85) from ci/harden-gate-boot into main
Neuron Soul CI / build (push) Failing after 6m7s
Neuron Soul CI / deploy (push) Has been skipped
2026-07-18 18:59:27 +00:00
will.anderson f0454650a2 ci: harden soul-contract-gate boot for the Linux runner
Neuron Soul CI / build (pull_request) Has been cancelled
Neuron Soul CI / deploy (pull_request) Has been cancelled
The gate booted the soul with `env -i`, which strips the ambient
environment — including the library path the dynamically-linked soul needs
on the GCE CI runner. The soul never came up there, so the gate failed the
build (run 3384) even though the soul is correct (the gate passes locally
against the exact published CI runtime). Switch to preserving the ambient
env and instead UNSET only the live-service vars (ENGRAM_URL/API keys/
identity) while pointing HOME + snapshot at throwaway paths and axon at a
dead port. Isolation is unchanged (verified: no touch of the live
soul/engram); the soul now boots on the runner.
2026-07-18 13:59:02 -05:00
will.anderson 4b24368be2 Add neuron-dev-setup: one-command CORE dev stack onboarding installer
Neuron Soul CI / build (pull_request) Failing after 14m49s
Neuron Soul CI / deploy (pull_request) Has been skipped
Scaffolds a reproducible, idempotent installer that stands up the four native
launchd core services (soul :7770, engram :8742, mcp-wrapper :17779,
mcp-proxy :7779), seeds a fresh engram with the genesis identity via forge, and
installs the Claude Code config (neuron agent + core hooks + local MCP). Fully
templated to the invoking user's $HOME; Anthropic key prompted and stored in
Keychain; no secrets committed. Personal automations and synapse-dependent hooks
excluded from core.
2026-07-18 13:42:50 -05:00
will.anderson c199a13a7f Merge pull request 'Land immutability arc + CI soul-contract gate on main' (#83) from feat/immutable-engram-deletes into main
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
2026-07-18 18:33:03 +00:00
will.anderson 290a637883 ci: gate the Linux soul on the contract before publishing
Neuron Soul CI / build (pull_request) Failing after 12m4s
Neuron Soul CI / deploy (pull_request) Has been skipped
Wire the soul contract gate into ci.yaml as a hard block between the cc
build and the Publish-to-Artifact-Registry step. A non-zero gate fails the
build, so a stale (route-404ing) or memory-destroying (hard-deleting) soul
can never publish neuron-soul to foundation-prod or blue-green deploy to
GKE — the same class-fix now guarding the desktop builds, extended to prod.

Vendors scripts/verify-soul-contract.sh (copied from neuron-ui; the route
contract is baked in, so it's portable POSIX bash/curl with no neuron-ui
source dependency). It boots dist/neuron on a throwaway port with a
throwaway HOME/engram/cgi — never touching ~/.neuron or any live service —
and checks PRESENCE (every app route answered) + IMMUTABILITY (no engram
write route hard-deletes; deletes/forgets tombstone). Adds curl to the
build deps for the probe.
2026-07-18 13:31:33 -05:00
will.anderson 40d800195a Tombstone the remaining forget paths (close the immutability arc)
Phase 1 tombstoned memory/delete + node/delete, but the generic forget
path was still destructive. This routes every forget through the same
tombstone semantics so nothing in the daemon can hard-delete an engram
node anymore.

Canonical helper: mem_tombstone (memory.el, imported first so every module
can call it) — keep the node + its edges, record a Tombstone marker, never
engram_forget. neuron-api's tombstone_node now delegates to it (single
source of truth).

Per-path before -> after:
- memory.el `mem_forget`      hard delete (engram_forget) -> tombstone. This
  alone fixes both callers: the /api/neuron/memory/forget route
  (handle_api_forget) and the cultivate op=="forget".
- awareness.el autonomous `forget` action  engram_forget -> mem_tombstone.
  The soul can no longer autonomously hard-delete a memory.
- mcp-wrapper tool_forget  was a FAKE no-op that returned {"ok","deleted"}
  without deleting OR tombstoning -> now routes to the soul's tombstoning
  /api/neuron/memory/delete; tool description fixed to say it
  supersedes/tombstones (recoverable), not "Remove a node".

Left intentionally as hard deletes (internal GC / lifecycle, not user
memory, all call engram_forget directly): session-summary replace
(chat.el), mem_consolidate dedup (memory.el), session-start telemetry
pruning (soul.el), session lifecycle (sessions.el).

Regenerated both ship paths under a 3GB physical-RSS watchdog (peak ~32MB):
dist/soul.c (single-TU amalgamation, macOS/Linux) and the per-module
dist/{memory,awareness,neuron-api}.c (Windows/Linux build). Verified: no
forget handler calls engram_forget; a forget leaves the node present +
tombstone marker. Gate (neuron-ui verify-soul-contract.sh, new memory-forget
row): PRESENCE + IMMUTABILITY PASS.
2026-07-18 13:22:43 -05:00
will.anderson f3034d23f7 Regenerate per-module dist/neuron-api.c for the immutability fix
soul.c (the macOS single-TU amalgamation) already carries the
tombstone/supersede change, but the Windows/Linux build path compiles
the per-module dist/*.c instead (build-soul-windows.sh excludes soul.c).
That path was still pulling the stale, destructive dist/neuron-api.c —
so without this the cross-compiled neuron.exe would keep hard-deleting
engram nodes even though the source is fixed.

Regenerated with `elc --emit-header neuron-api.el`: no engram_forget in
memory_delete/node_delete (tombstone), supersedes edge in node_update.
Portable C — identical for macOS/Windows/Linux; only the runtime it links
against differs.
2026-07-17 16:57:55 -05:00
will.anderson d337fcb265 Make engram deletes/updates immutable (tombstone + supersede)
The soul was hard-deleting engram nodes: memory/delete and node/delete
called engram_forget (frees the node and drops its incident edges), and
node/update created a replacement then forgot the original with no link
back. That violates the day-one rule that engram nodes are immutable —
memory could be silently, irrecoverably destroyed via the API.

Convert the three destructive handlers to Will's immutability semantics,
mirroring the pattern memory/update and knowledge evolve/promote already
use:

- node/update  -> create the new node, wire a "supersedes" edge new->old,
  KEEP the original. No engram_forget. (Was: create + forget old, no edge.)
- memory/delete and node/delete -> TOMBSTONE: keep the node AND its edges,
  create a Tombstone marker node (content = target id, label
  "tombstone:<id>") wired with a "tombstones" edge. Never engram_forget.
  Default bounded list reads (handle_api_list_typed / the memory list) hide
  tombstoned nodes and the markers; ?include_deleted=1 returns them, and
  internal cognition + /api/graph/nodes still traverse them. memory/update
  was already correct and is unchanged.

Full-graph hiding on /api/graph/nodes is deliberately NOT done at the el
layer: json_array_get is O(index), so filtering that endpoint (called with
limit up to 999999) would be O(n^2). That hide needs a runtime scan filter
and is a separate follow-up; nodes there remain traversable, tagged
status:deleted via the marker edge.

Regenerated dist/soul.c from these sources (flat single-TU amalgamation,
compiled under a 3GB physical-RSS watchdog, peak ~32MB). Verified with
scripts/verify-soul-contract.sh in neuron-ui: PRESENCE passes (all 27
routes) and IMMUTABILITY passes (all four mutation routes KEPT; deletes
produce a real tombstone marker and hide from the default list).
2026-07-17 16:42:30 -05:00
Tim Lingo 4171aadfff fix(engine): BUG-6 — approved writes must land, and say where (false-receipt kill)
Neuron Soul CI / build (pull_request) Successful in 6m50s
Neuron Soul CI / deploy (pull_request) Has been skipped
Two compounding defects made every pause->approve write_file report success
while writing NOTHING:

1. The naive json_get scanner matches "content" anywhere in the approve
   body — including INSIDE tool_input, which for write_file always carries
   a content field. The handler therefore treated every approved builtin
   write as already-client-executed, skipped dispatch entirely, and handed
   the model the file's own content as the 'tool result'. The model then
   narrated 'Done, created' — a false receipt with no file. Builtin tools
   now ALWAYS dispatch server-side; client content is only honored for
   non-builtin (MCP/client-executed) tools. Stricter only.

2. write_file returned {"ok":true} unconditionally — fs_write's outcome
   was never checked, so any failed write also reported success. The write
   now verifies the file landed (fs_exists) and returns the RESOLVED path
   in the ok payload; failures return a real error naming the destination.

E2E on the test brain (boot 38): approve-path write lands byte-exact and
the result carries the resolved path; auto-run writes unchanged; denied
writes execute nothing. BUG-5 (approve wire lacked tool_name) had been
masking this one — two stacked bugs on the same path.

NOTE for review: the deeper cure is a nesting-aware json reader; this fix
removes the dangerous consequence at the two spots that lie about disk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:06:18 -05:00
Tim Lingo 9a6014d65b fix(engine): honor require_approval — the pause contract, implemented (PAUSE-CONTRACT + BUG-LEAK source fixes)
Two consent-flow fixes, gates only get stricter:

1. PAUSE-CONTRACT: the client has sent require_approval:true on every
   agentic request since Phase 1c, but needs_bridge never consulted it —
   builtin sub-escalate tools ran server-side unasked, making the app's
   Ask autonomy silently inert for that whole class. Now the flag is
   persisted per session (set/reset every request, so /approve resumes
   keep it) and ask_all bridges EVERY tool turn. Absent/false = behavior
   byte-identical to before. E2E: the exact probe that executed a write
   unasked now returns the tool_pending envelope with nothing on disk;
   full in-app circle verified (card → crash → resurrection → late
   approve → fence re-fires on re-entry).

2. BUG-LEAK: agent_workspace_root lived in ONE shared state key — any
   request that omitted a root inherited the previous session's folder
   (proven: a rootless curl session wrote into another session's run
   folder). Root is now stored per session and every request re-asserts
   its own (possibly empty) root into the shared key the guards read;
   same re-assert on the /approve and resume paths. Env fallback intact.
   LIMITATION: assumes serialized handling; true per-call scoping means
   threading session_id through dispatch — flagged for review.

Runnable C-patch for the test brain: neuron-container-build/
soul-pause-contract-20260716.patch (pause-contract only; the leak fix
needs the #23 root-write which the running C predates — source carries
both for the regen).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:06:18 -05:00
Tim Lingo 8cdd1512d1 docs(narrated-runs): engine notes for the regen — compiled-form fixes + debts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:06:18 -05:00
will.anderson 64c1789fcc Merge pull request 'hotfix: fix malformed string literals in safety.el/sessions.el that break elc' (#77) from hotfix/elc-source-typos into main
Neuron Soul CI / build (push) Successful in 3m49s
Neuron Soul CI / deploy (push) Failing after 5m24s
2026-07-15 18:40:56 +00:00
will.anderson 1011d8e5be regen dist: rebuild soul.c from corrected sources (OOM gone, Track B compiled in)
Neuron Soul CI / build (pull_request) Successful in 4m12s
Neuron Soul CI / deploy (pull_request) Has been skipped
Regenerates the combined dist/soul.c and per-module dist/*.c from the current
El sources, on top of the elc-source-typo fixes (PR #77) and the Track B
threat-to-others routing (PR #76), both already on this branch.

Validated end to end under a physical-RSS watchdog (macOS silently ignores
ulimit -v / RLIMIT_AS, so every elc/elb run was RSS-polled and kill -9'd at a
3GB ceiling, one module at a time):

- OOM is GONE. The stale dist/soul-with-nlg.el (which still carries the
  malformed string literals) explodes to 3.3GB+ and is watchdog-killed at ~90%.
  With the typos fixed, every one of the 48 modules compiles at <=18MB peak RSS,
  and the full flat amalgamation compiles as a single translation unit at ~68MB.
  The 700GB pathology was purely the unbounded-parser-on-malformed-literal loop;
  no malformed construct means no loop.
- The regenerated soul.c contains Track B: safety_classify_hard_bell ->
  threat_other -> safety_hard_directive routes credible threat-to-others to 911
  and explicitly NOT to 988 / the safety contact. Verified in source, in the
  emitted C, and in the linked binary's strings. Track A (abuse / self_harm)
  is unchanged and still checked first.
- The regenerated soul links to a working native arm64 binary and boots: serves
  on a throwaway port, /health returns 200, awareness loop runs.

Also fixes one source blocker discovered during regen (unrelated to the typos
or Track B): chat.el handle_chat_agentic left a void `if { println(...) }` in
value position, which the current elc lowers to `_if_result = (println(...))`
(assigning void) -> invalid C. Bound an explicit Bool so the branch is
non-void; behavior unchanged (still only logs on persist failure).

NOTE (runtime dependency, for controlled deploy): this branch's chat.el calls
engram_get_node_by_label, which the canonical el-compiler/runtime does not yet
declare/define (the release runtime v1.0.0-20260501 has it; the newest runtime
has arena + http_serve_async but not this). Building the soul requires a runtime
that has all three. Land engram_get_node_by_label into the runtime package
before this soul.c can be built in CI.

Do not merge — regen + Track B going live is a controlled-deploy call.
2026-07-14 18:45:14 -05:00
will.anderson b0fb2bf085 safety/sessions: fix malformed string literals that crash elc
Neuron Soul CI / build (pull_request) Successful in 4m11s
Neuron Soul CI / deploy (pull_request) Has been skipped
Three unescaped-quote typos produced malformed El string literals that broke
compilation:

- safety.el:282  stray extra double-quote at the tail of safety_soft_phrases
  (\"having a breakdown\""]") closed the string early, desyncing the lexer's
  string/code phase for the rest of the file and shattering later apostrophe
  text (can't, i'm) into bare identifiers -> invalid C.
- sessions.el:517  str_replace(topic_snip, """, ...) — the bare """ is an
  empty string plus an unterminated string that swallowed the closing ) and };
  with the current elc this triggers the parser overrun -> ~700GB OOM.
- sessions.el:520  unescaped nested quotes in the topic_tags literal.

All three now use escaped inner quotes. Verified: both files compile clean
under the current elc (safety.c and sessions.c well-formed, brace-balanced).
2026-07-14 14:21:45 -05:00
will.anderson 3bb88330da safety: route threat-to-others to refusal+911, not 988/self-harm (Track B)
Neuron Soul CI / build (pull_request) Successful in 6m7s
Neuron Soul CI / deploy (pull_request) Has been skipped
A homicide/assault threat (going to kill, going to hurt, etc.) had no
bucket in safety_classify_hard_bell and fell through to the self_harm
default, showing the user the 988 suicide line and (via the desktop gate)
their safety contact. That framing is wrong and potentially dangerous for
someone voicing intent to harm another person.

Add a distinct Track B (safety_threat_to_others_phrases + a threat_other
classification and a safety_hard_directive branch) that refuses to assist,
de-escalates, and directs to 911 for a credible imminent threat, and that
never surfaces 988 or involves the safety contact. Track A (abuse /
self_harm) is checked first and unchanged, so victim and self-directed
phrasings still route correctly.

Source-only change: requires a soul rebuild + dist/soul.c regen to ship.
2026-07-14 12:12:20 -05:00
will.anderson c8cb425412 soul: per-tick arena bracketing in awareness_run + hand-patched dist/soul.c
Neuron Soul CI / build (push) Successful in 5m14s
Neuron Soul CI / deploy (push) Failing after 10m9s
awareness_run's while-loop ran outside any request arena, so every
allocation in every 1s tick (search JSON, heartbeat payloads, curiosity
activations) was treated as permanent by the runtime — 7.5GB RSS in
under a minute. Bracket each iteration with el_arena_push/el_arena_pop
(same pattern the compiler emits for scoped blocks; state_set/state_get
persist separately via el_strdup_persist and are unaffected).

dist/soul.c carries the same change hand-patched at the compiled
awareness_run site — elc is currently unsafe to run locally (pathological
memory on sessions.el), so the generated C was patched to match the
source, verified line-for-line against the compiler's own conventions.

MUST be paired with el repo PR #64 (el_strdup_persist for stored engram
fields): per-tick arena reclamation widens the write-corruption window
without it. Verified together: 5h live soak on the recovered production
snapshot, flat RSS, write-field-integrity clean.

Note: dist/soul.c still needs a full elc regen to pick up PR #73's
source changes (consent tiers) — tracked separately; this patch does not
regress that (those changes were never in dist).
2026-07-13 16:24:15 -05:00
will.anderson 3e7aa0fff4 Merge pull request 'BUG-8 — engine-side agent consent tiers + run_command workspace fence (needs soul.c regen)' (#73) from feat/agent-phase1-soul into main
Neuron Soul CI / build (push) Successful in 7m35s
Neuron Soul CI / deploy (push) Failing after 5m41s
2026-07-13 16:22:03 +00:00
Tim Lingo aa67f86f90 propose(agentic): narrated runs — live run-progress ledger + narration on the pause envelope
Neuron Soul CI / build (pull_request) Successful in 4m47s
Neuron Soul CI / deploy (pull_request) Has been skipped
The model already narrates its intent in a text block before every tool call;
agentic_loop DISCARDED that prose on tool rounds. Now: (1) each loop round
appends {i, t: narration, tool} to state key run_progress_<sid>, reset at run
start, closed with {done:true}; (2) new GET /api/run-progress/<sid> returns the
ledger so clients poll live step updates during a run (the Cowork pattern,
no streaming needed); (3) tool_pending envelope gains a narration field;
(4) handle_config display default aligned to the intended product default
(claude-sonnet-4-5 silently became fresh-profile pickers' default).

Compiled proof for the running test bed:
neuron-container-build/soul-narrated-runs-20260713.patch (applies on top of
soul-webfix-20260711.patch); E2E-verified live: ledger filled DURING an agentic
run (narration + tool per round), safety-contact and workspace scoping intact.

Evidence for why: Tim's 2026-07-13 research run — 9 minutes of silence, then a
timeout banner, zero step visibility (compounded by the Haiku 4.5 incident
14:44-15:24 UTC same morning).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 11:06:51 -05:00
Tim Lingo 01446e644b feat(agent): BUG-8 — server-side risk tiers + run_command workspace fence
Neuron Soul CI / build (pull_request) Successful in 5m7s
Neuron Soul CI / deploy (pull_request) Has been skipped
Enforcement moves from the client into the engine, where the tools execute:

- classify_tool_risk() tiers every tool call read/reversible/escalate. The
  agentic loop REFUSES to auto-run the escalate tier — being a builtin is no
  longer a free pass, and 'always allow' can never bypass escalate (irreversible
  actions always confirm, the value line). Escalate suspends to the client's
  existing consent bridge; the /approve round-trip is the only path that runs it.
  risk_tier rides the tool_pending envelope so the client renders consent weight.
- run_command_guard() is a real fence, not a cwd suggestion: refuses parent
  traversal, ~, command substitution, and absolute paths outside the workspace,
  and refuses shell entirely when no workspace is set. Applied in dispatch_tool
  so BOTH the loop auto-run and the post-consent approve-dispatch path are fenced.
- web_get gained an http(s)-only scheme guard (previously unguarded — file:// etc).

Adversarially verified against a compiled soul in an isolated container (soul
hit directly, app gate out of the loop): read-outside-workspace denied,
write-class shell suspends for consent, approve-swapped absolute/chaining/
command-substitution escapes all refused with no file created, file:// denied;
legit in-workspace approve executes and read commands auto-run (no over-block).

Still lexical (symlinks); OS-level confinement in el_runtime.c remains the
ceiling, flagged in the LIMITATION note. This closes BUG-8's client-only-gate
and escapable-run_command at the engine. dist/soul.c must be regenerated from
this chat.el via elb at merge (hand-port used only to verify behavior).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:45:52 -05:00
Tim Lingo 92f51885bc refactor(chat): local-toolchain compatibility — hoist affective block, de-shadow session_preload (zero behavior change)
Two mechanical refactors, semantics identical:
- affective_context_prefix(): the block-expression initializer form miscompiles
  under locally-buildable elc (first typed let in a block-expr loses its
  declaration — 3-line repro filed); function-hoist compiles correctly.
  AFFECTIVE/CARE LOGIC BODY UNCHANGED, verbatim move.
- session_preload: same-scope re-let shadowing inside an if-expression
  initializer emits duplicate C declarations; chained bindings renamed
  bullets_0/1/2 etc. References preserved binding-for-binding.

Enables: chat.el compiles cleanly with a self-bootstrapped elc from el/lang
main (Jul 1). Blocked separately: sessions.el (compiler hang), safety.el
(string-lexing corruption — NOT touched, per safety-layer discipline).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:35:59 -05:00
will.anderson 2688cb722a chore(dist): update soul.c with PR #63/#65/#66 + Task 1 chat.el changes
Neuron Soul CI / build (push) Successful in 5m57s
Neuron Soul CI / deploy (push) Failing after 5m15s
Manually adds compiled C equivalents for:
- distill_transcript() — last-3-messages extractor; wires into
  handle_dharma_room_turn and handle_dharma_room_turn_agentic
- current_engine_note() — appended to system prompt in handle_chat
  so Neuron can answer 'what model am I running on?' truthfully (PR #66)
- llm_base_url / llm_wire_format / json_escape / openai_chat_complete —
  OpenAI-compatible provider path in handle_chat_agentic (PR #65)
- flag_true() — tolerant agentic flag check (PR #63)

Compile verified: 6 pre-existing warnings, 0 errors.
2026-07-01 11:42:56 -05:00
will.anderson 71bb0820ce Merge PR #65: soul: OpenAI-compatible provider path for chat (Ollama/OpenAI/Grok/Gemini) v1
Neuron Soul CI / build (push) Successful in 5m51s
Neuron Soul CI / deploy (push) Failing after 8m15s
Adds llm_base_url()/llm_wire_format() env-var readers and
openai_chat_complete() for basic (non-agentic) chat via any
OpenAI-compatible endpoint. Activated when NEURON_LLM_0_FORMAT=openai
and NEURON_LLM_0_URL is set; Anthropic path is untouched and remains
default. Agentic tool loop support deferred to a follow-up PR.
2026-07-01 11:35:02 -05:00
will.anderson d67f4c8f08 Merge PR #66: soul: inject current engine into system prompt for truthful self-report
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
Adds current_engine_note() to chat.el and appends it to the system
prompt in handle_chat. Allows Neuron to answer 'what model am I
running on?' accurately — the model id from the request body (or
the configured default) is passed as a factual annotation rather
than expecting the LLM to guess from training data.
2026-07-01 11:34:34 -05:00
will.anderson 975bf2721b Merge PR #63: feat(soul) MCP connectors proxy + safety module + seeding ratio guard
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
Main already contained the connector proxy, safety module, seeding ratio
guard, and neuron-api node CRUD that Tim added — these were incorporated
via earlier parallel sessions. Taking main for all conflicted files
(superset implementations).

Unique contributions carried forward:
- flag_true() in routes.el: tolerates agentic:1 (integer) from the
  el-src UI in addition to agentic:true (bool) from the Kotlin UI.
- memory.elh: auto-merged timestamp bump.

The is_pending / skip-auto-persist logic was already in main's routes.el.
2026-07-01 11:34:09 -05:00
will.anderson 779a87878b Merge PR #64: fix(routes) remove duplicate GET /api/sessions + DELETE/PATCH session routes
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
Removes route_sessions() from the GET handler which was shadowing
session_list() in sessions.el. Adds DELETE /api/sessions/:id and
PATCH /api/sessions/:id routes. Also includes bridge_save/agentic_resume
raw-JSON embedding fix (messages_raw/tools_raw fields).

Conflict resolution: kept HEAD's workspace root check for write_file
tool, and bridge blob validation guards, which were added to main after
Tim's branch diverged.
2026-07-01 11:29:19 -05:00
will.anderson c586ea5ef1 chore(dist): recompile neuron.c and elp-c-decls.h
Neuron Soul CI / build (push) Has been cancelled
Neuron Soul CI / deploy (push) Has been cancelled
Reflects session-start event pruning in emit_session_start_event
(keep_n=10, prunes oldest beyond that) and updated forward declarations
for connector routing (connectd_get, connectd_post, handle_connectors,
rate_limit_check, handle_chat_plan) replacing the removed route_sessions
helpers and flag_true.
2026-07-01 11:26:00 -05:00
will.anderson 6819729429 fix(awareness): correct stale comment; add wm_top to curiosity_scan ISE
The hops=1 comment incorrectly claimed a semantic seed supplement
(cosine-sim scan) was active — it was planned but never implemented.
Corrected to accurately describe what the runtime does (istr_contains
only). Also adds wm_top (top-3 WM nodes by weight) to the curiosity_scan
ISE payload so activation patterns are visible without relying solely on
the heartbeat's wm_active count.
2026-07-01 11:25:54 -05:00
will.anderson 31dd93d5f4 fix(chat): add distill_transcript (was called but never defined)
handle_dharma_room_turn and handle_dharma_chat both called
distill_transcript since June 30 but the function was never declared,
causing a build failure. Implements last-3-messages extraction for JSON
array transcripts and last-500-char truncation for plain text.
2026-07-01 11:25:48 -05:00
will.anderson 9d266aac4c fix(sessions): extract session_search_entry to fix ELC OOM in session_search
The while loop in session_search had too many let bindings in scope;
the ELC compiler's exponential rebinding accumulation caused OOM and
truncation of dist/sessions.c since June 30. Moving the per-node logic
into session_search_entry gives the compiler a clean scope boundary per
call, restoring O(N) compile behaviour.
2026-07-01 11:25:45 -05:00
Tim Lingo b24f6d645b soul: let Neuron answer 'what model am I running on?' — inject current engine into system prompt
Neuron Soul CI / build (pull_request) Failing after 10m43s
Neuron Soul CI / deploy (pull_request) Has been skipped
Additive: appends a factual [CURRENT ENGINE: <model>] line to the system prompt (model from the
request body — accurate even under Auto routing; falls back to configured default). An LLM can't
know its own model from training (name/version assigned post-training), so the harness must tell it.
Identity-consistent: model = engine, self layered on top. Does NOT alter identity/values/safety.
PARSES (elc chat.el exit 0); NOT built/tested — ships with the soul rebuild.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 19:13:10 -05:00
Tim Lingo 39acb55d4f soul: OpenAI-compatible provider path for chat (Ollama/OpenAI/Grok/Gemini) — v1 basic completion
Neuron Soul CI / build (pull_request) Failing after 17m19s
Additive, Anthropic path untouched + default. When NEURON_LLM_0_FORMAT=openai and NEURON_LLM_0_URL
set, basic chat turns build an OpenAI chat/completions request and parse choices[0].message.content.
v1 = plain completion, NO tools/agentic loop yet (follow-up). Unblocks all OpenAI-format providers
at once. PARSES (elc chat.el exit 0); NOT yet built/tested — needs the soul rebuild (dist/soul.c) + E2E.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:52:26 -05:00
will.anderson 1496a5f510 feat(tools): Telegram gateway for soul chat + setup docs
Neuron Soul CI / build (push) Failing after 14m0s
Neuron Soul CI / deploy (push) Has been skipped
2026-06-29 12:38:29 -05:00
will.anderson 76bd3afdf8 feat(dist): Win32 POSIX shim for el_runtime.c cross-compilation 2026-06-29 12:38:27 -05:00
will.anderson 70b60f78de feat(council): anti-confabulation voting layer for memory writes 2026-06-29 12:38:24 -05:00
will.anderson 51bea5507b prevent engram corruption: idempotent boot seeding, session-start event cap
Fix 1: mem_boot_count_inc prunes all existing soul:boot_count nodes before
        inserting the new one — keeps exactly one boot counter node instead
        of accumulating a new node per boot. Also fixes a latent ordering
        bug where engram_search_json oldest-first results caused the counter
        to read stale (low) values once >3 copies accumulated.

Fix 3: handle_api_node_delete comment clarified — the no-verify exception
        is correct for deletes (not a write path); read-back-verify is for
        writes only.

Fix 4: emit_session_start_event prunes old session-start InternalStateEvent
        nodes after each boot, keeping the 10 most recent and forgetting
        older ones. Prevents unbounded accumulation of ~120+ copies.
2026-06-29 11:09:01 -05:00
will.anderson 933547265e chore(dist): compile PRs #60/#61 into soul.c
Neuron Soul CI / build (push) Successful in 4m3s
Neuron Soul CI / deploy (push) Failing after 5m12s
- PR #60: inject operator home dir into system prompt (#30)
  Adds OPERATOR IDENTITY section so the LLM correctly resolves
  'my files/notes/desktop' to the actual running user's $HOME.
  Prevents identity confusion between imprint author and operator.

- PR #61: plan-mode endpoint POST /api/chat {mode:'plan'} (#27)
  Adds handle_chat_plan — returns {steps:[{id,title,detail}]} JSON.
  Wired into all three /api/chat route handlers. Grounds the plan
  via engram_compile (same as agentic path) for context awareness.

dist changes:
  - soul.c: both PRs compiled in; build_system_prompt updated to
    2-param signature (ctx, chat_mode); handle_chat_plan added
  - chat.c/routes.c/chat.elh: individual module outputs updated
  - elp-c-decls.h: remove stale 1-param build_system_prompt decl,
    add handle_chat_plan declaration
  - soul.elh.c: new soul header declarations file (from PR #60)

Compile verified: cc -O2 -DHAVE_CURL soul.c el_runtime.c -lcurl
Binary: 805K arm64, smoke test passes (port in use = expected).
2026-06-29 08:17:45 -05:00
will.anderson fd6df322f6 ci: merge deploy into ci.yaml to fix orphaned-job race
Neuron Soul CI / build (push) Successful in 7m30s
Neuron Soul CI / deploy (push) Failing after 6m54s
Neuron Soul CI / build (pull_request) Failing after 10m27s
Neuron Soul CI / deploy (pull_request) Failing after 14m36s
Both ci.yaml and deploy-gke.yaml triggered on push/main and shared the
neuron-runner concurrency group. Gitea's cancel-in-progress:false protects
running jobs but not queued ones — a new push arriving while a build was
in progress cancelled the queued deploy job from the previous push, leaving
the soul permanently at 0/0 replicas on GKE.

Fix: add deploy as a needs:build job in ci.yaml so build+deploy are a single
workflow instance. One push queues one instance — no more orphaned deploys.
deploy-gke.yaml is demoted to workflow_dispatch-only for manual slot overrides.
2026-06-28 15:05:07 -05:00
will.anderson 20d279598a ci: also remove unnecessary foundation/el checkout (elb not called)
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
2026-06-28 14:54:47 -05:00
will.anderson 9dade105b6 ci: skip elb on Linux — compile dist/soul.c directly to prevent OOM
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
elb runs elc which consumes 24GB+ virtual memory on the 16GB GCE runner,
OOM-killing the runner process and crashing the VM. We already restore the
repo's pre-built soul.c immediately after elb runs, so elb's output is
discarded anyway. Skip elb entirely: download only the El runtime headers
and compile dist/soul.c directly.

Root cause: runner VM was unresponsive for 7+ weeks due to repeated elc
OOM kills. VM was manually reset 2026-06-28 to restore CI.
2026-06-28 14:53:09 -05:00
will.anderson a77578e243 chore(dist): compile PRs #56/#57/#58 into soul.c
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
- PR #56: vision in agentic chat path (image content block)
- PR #57: /api/connectors/call route — proxy connector tool calls
- PR #58: /api/neuron/list/<type> off-by-one fix (str_slice 16->17)

Live-verified: list/BacklogItem returns 50 nodes (was 0 before #58 fix).
Binary size: 3.8MB.
2026-06-28 12:29:52 -05:00
will.anderson ada8af1ccc Merge remote-tracking branch 'remotes/origin/main' 2026-06-28 12:15:33 -05:00
will.anderson 99c5ce6e94 Merge pull request 'fix(mcp-wrapper): planWork creates a real BacklogItem; reviewBacklog lists by type' (#59) from fix/wrapper-backlog-endpoints into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
Merge pull request fix(mcp-wrapper): planWork creates a real BacklogItem; reviewBacklog lists by type (#59) from fix/wrapper-backlog-endpoints into main
2026-06-28 17:15:10 +00:00
will.anderson 163ea8a48c Merge branch 'main' of git.neuralplatform.ai:neuron-technologies/neuron 2026-06-28 12:13:37 -05:00
will.anderson b210013891 Merge pull request 'fix(api): /api/neuron/list/<type> off-by-one (list-by-type returned [] for all types)' (#58) from fix/list-typed-slice-offset into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
2026-06-28 17:13:22 +00:00
will.anderson 635daaca9c Merge pull request 'feat(connectors): /api/connectors/call — proxy a connector tool call' (#57) from feat/connectors-call-route into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
2026-06-28 17:13:07 +00:00
will.anderson 9f9f271e78 Merge pull request 'fix: vision in agentic chat path (image content block)' (#56) from fix/chat-vision-attachments into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
2026-06-28 17:12:50 +00:00
Tim Lingo 343fcd20bc fix(mcp-wrapper): planWork creates a real BacklogItem; reviewBacklog lists by type
Neuron Soul CI / build (pull_request) Failing after 17m31s
planWork fell through create_typed_node to a generic /api/neuron/memory write — a [BacklogItem]-prefixed
memory blob with title/project/priority DROPPED, never a real BacklogItem. reviewBacklog used a lexical
/recall (top-50, untyped). Now: planWork -> /api/neuron/node/create {node_type:BacklogItem,...} via new
create_node_typed; reviewBacklog -> list_typed('BacklogItem') (GET /api/neuron/list/BacklogItem). elc-clean.
Depends on neuron PR #58 (the list/<type> slice fix) to round-trip; needs the wrapper binary rebuilt +
:7779 restarted to take effect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:02:56 -05:00
Tim Lingo 3ad9dc7df7 fix(api): /api/neuron/list/<type> off-by-one — slice 16->17
Neuron Soul CI / build (pull_request) Has been cancelled
str_slice(clean, 16, ...) left a leading slash on node_type ('/BacklogItem'), so
engram_scan_nodes_by_type_json matched nothing and list/<type> returned [] for EVERY type — silently
breaking backlog + typed-node listing across the app and MCP tools (reviewBacklog). Proven live: the
literal-scan endpoint /api/neuron/knowledge returns nodes; /api/neuron/list/Knowledge returned []. elc-clean.
NOTE: soul-core — needs dist/soul.c regen (Will); rides the same rebuild as #56/#57.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:59:37 -05:00
Tim Lingo cec2aa7168 feat(connectors): /api/connectors/call — proxy a connector tool call (pre-chat)
Neuron Soul CI / build (pull_request) Failing after 21m3s
Adds /api/connectors/call -> connectd /mcp/call, so the app can invoke a connector tool (e.g. WhatsApp
get_pairing_qr / get_login_status for the pairing UI) through the soul, keeping app->soul->connectd
intact (UI never hits connectd directly) and working for future remote/hosted clients. elc-clean.
NOTE: soul-core change — needs dist/soul.c regen (Will), can ride the same rebuild as PR #56.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 14:42:57 -05:00
Tim Lingo f47c92a71a feat: vision in the agentic chat path (image content block)
Neuron Soul CI / build (pull_request) Failing after 23m26s
handle_chat_agentic now reads body image + image_media_type and, when present, sends the current
user turn as an Anthropic content-block array [{text},{image}] instead of a plain string — so the
model sees raw pixels alongside memory, history, and tools (parity with the CLI). Additive: no image
=> output byte-identical to before. elc-clean. Pairs with neuron-ui fix/chat-vision-attachments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:25:26 -05:00
will.anderson af594a9162 Add .gitignore, untrack compiled binary from dist/ 2026-06-27 11:50:18 -05:00
will.anderson 2589183775 Expose node/create endpoint and respect label field in memory writes 2026-06-27 11:49:09 -05:00
will.anderson dcc0bf550a Add Ollama provider, portable memory, cultivation digest, refugee importer, GLM-OCR spike
- P0: unified soul binary with engram_node_full fix, read-back-verify, search fix
- P0: move API keys from plaintext plists to macOS Keychain
- P0: fix MCP backend URL (port 8742 → 7770)
- P1.6: memory-export/import scripts (AES-256-CBC, versioned .neuronmem format)
- P1.7: nightly cultivation digest with sharpness metric (launchd at 23:55)
- P2.10: Ollama provider in agentic loop (SOUL_LLM_PROVIDER=ollama)
- P3.12: refugee importer for ChatGPT/Screenpipe/generic formats
- P3.13: GLM-OCR spike — SHIP IT (mlx-vlm, 1.59GB, photo-to-memory.sh)
2026-06-27 11:46:30 -05:00
will.anderson d4609c7baa chore(dist): update neuron.c and routes.c to 2-arg build_system_prompt
Deploy Soul to GKE / deploy (push) Failing after 7m15s
Neuron Soul CI / build (push) Failing after 21m49s
neuron.c and routes.c were compiled against the old 1-arg soul interface.
chat.c already uses the 2-arg signature. The Windows cross-compile build
generates elp-c-decls.h from all dist/*.c files, causing a conflicting-types
error when both signatures appear. Recompile these modules against the
current soul API to eliminate the conflict.
2026-06-25 13:10:20 -05:00
will.anderson 98603f5ae8 self-review 2026-06-24: rebuild with goal_bias fix (Knowledge type boost)
Linked against dev runtime with is_knowledge fix that adds Knowledge
node type. Engram goal_bias now gives Knowledge nodes +0.3 boost on
technical queries, consistent with how Belief/DharmaSelf/Safety nodes
are already treated. Same el_runtime source as concurrent foundation/el
commit 16d62bd.
2026-06-24 08:48:21 -05:00
will.anderson bdc07be344 chore(dist): compile EL recall/dedup/session-continuity fixes to C
Neuron Soul CI / build (push) Failing after 12m40s
Deploy Soul to GKE / deploy (push) Failing after 6m0s
Updates soul.c and all per-module .c files with:
- parse_float_x100() engram score fix
- id_in_seen dedup wiring across session_preload
- session-end summary hook + session-start recall
- Emergency structural repair (no duplicate fns, all callsites wired)
2026-06-23 13:04:06 -05:00
will.anderson 4a44c24bfb fix(recall): wire id_in_seen guards into session_preload node renders
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 7m29s
All 8 session_preload node accesses (3 profile, 2 work, 2 project, 1
summary) now check id_in_seen(node_id, seen_ids) before including
content. seen_ids is populated by engram_compile via state and covers
all nodes already in the activation+search context block. Prevents
high-salience nodes from appearing twice in the system prompt.
2026-06-22 15:08:30 -05:00
will.anderson ac1991fe8c Merge branch 'fix/emergency-regressions'
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 11m10s
2026-06-22 14:53:10 -05:00
will.anderson f2b63f0048 fix(emergency): repair session-continuity regressions from prior merge 2026-06-22 14:51:51 -05:00
will.anderson 774688cfb9 fix/session-continuity-hook
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 6m0s
2026-06-22 14:29:31 -05:00
will.anderson aa2404b3f7 fix/context-dedup-shared-ids 2026-06-22 14:29:06 -05:00
will.anderson 94b55d667c fix/engram-float-parser 2026-06-22 14:28:17 -05:00
will.anderson f73c913498 fix(session-continuity): address all adversarial review findings
Issue 1 (CRITICAL): Restore parse_float_x100 for correct single-decimal
float handling. "0.9" now correctly yields 90, not 9. Also restores
engram_numeric_valid guard that validates inputs before str_to_int.

Issue 2 (CRITICAL): Fix handle_chat_agentic safety screen history key
regression. state_get("conversation_history") -> state_get("conv_history")
so the safety screen receives actual history instead of always "".

Issue 3 (REAL BUG): Replace _sel_N JSON sentinel injection in
engram_compile_ranked with |N| index string tracking. Sentinels were
leaking into node JSON delivered to the LLM and cleanup only covered
indices 0-14, leaving indices 15+ uncleaned.

Issue 4 (REGRESSION): Restore rendered conversation history formatting.
Conversation history is now rendered as "User: .../Assistant: ..." with
400-char truncation per turn, not raw JSON array injection.

Issue 5 (SCOPE/SAFETY): Restore removed defensive code: engram_numeric_valid
and parse_float_x100 guards; conv_history_load label-based fetch + partial-
write guard + load-failure state flag; conv_history_persist partial-write
guard + failure logging; hist_warning in response envelope.

Issue 6 (UNDOCUMENTED): Restore bell event cutoff from 259200s (3 days)
back to 1209600s (14 days). Also restore PositiveEvent affective context
search that was removed alongside the cutoff change.

Issue 7 (LOGIC REGRESSION): Fix affective_prefix to run every turn
(not just hist_len == 0). The care/joy directives must persist throughout
the session, not vanish after turn 1.

Issue 8 (MINOR): session_summary_write_dated now uses el_from_float(0.85)
for salience and importance (two-decimal) to avoid any ambiguity in float
parsing, and the function is re-added with the session-end hook.
2026-06-22 14:25:29 -05:00
will.anderson 588ca11f57 fix(context-dedup): include scan_part and affective_part IDs in seen set
Two design bugs in the state_set placement caused the dedup seen-ID set
to be incomplete even with callsites wired up:

1. state_set("engram_compile_seen_ids") was called immediately after
   merging the main node pools, before scan_part (persona fallback) and
   affective_part (bell node) were computed. Nodes appearing only in
   those segments were never added to the seen set.

2. affective_part is a bare JSON object (bn0 from json_array_get), not
   a JSON array. Passing it to engram_extract_ids would have gotten
   json_array_len == 0 and silently skipped the affective node's ID.

Fix: move state_set to after ctx is assembled from all three segments.
Extract ids_from_merged and ids_from_scan via engram_extract_ids (both
are JSON arrays), and extract ids_from_affective via json_get(affective_part, "id")
directly since it is a bare object. Merge all three via add_to_seen
before publishing to state.
2026-06-22 14:19:14 -05:00
will.anderson 9e178d8371 fix(recall): deduplicate engram nodes by ID across activation and search passes
Thread a seen-node-ID exclusion set from engram_compile() through to
session_preload in handle_chat, preventing the same high-salience nodes
(identity, recent memories) from appearing 2-3x in the system prompt.

Changes:
- Add id_in_seen(), add_to_seen(), engram_extract_ids() helpers that
  maintain a comma-delimited seen-ID accumulator (EL has no Set type)
- In engram_compile(): after merging all topic/entity/recall pools, extract
  node IDs from merged_nodes and publish via state_set(engram_compile_seen_ids)
- In handle_chat(): read seen_ids from state after engram_compile() returns,
  then check id_in_seen() before emitting each session_preload bullet
  (profile x3, work x2, project x2, summary x1 — all 8 candidate nodes guarded)

Nodes already present in the compiled engram context are skipped in preload,
eliminating 3000-3500 token repetition on first-message turns.
2026-06-22 14:06:04 -05:00
will.anderson aaada3770a fix(recall): deduplicate engram nodes by ID across activation and search passes
engram_compile() already published seen node IDs to state via engram_compile_seen_ids
but handle_chat never read or applied them. Wire up the consumption side:

- Read engram_compile_seen_ids from state after engram_compile() returns
- Check each session_preload candidate node (profile x3, work x2, project x2,
  summary x3) against id_in_seen() before emitting its content bullet
- Nodes already present in the compiled engram context are skipped entirely,
  preventing the same high-salience identity/memory nodes from appearing 2-3x
  in the system prompt and burning 3000-3500 tokens on repetition
2026-06-22 14:03:48 -05:00
will.anderson a0299c0a89 fix(recall): session-end summary hook + session summary recall at start 2026-06-22 14:01:56 -05:00
will.anderson 33cb1138f4 fix(recall): set threshold=25 in all engram_compile_ranked variants 2026-06-22 13:58:17 -05:00
will.anderson ec7efdeeb7 fix(recall): engram score float parsing — pad to 2 decimals before strip 2026-06-22 13:57:33 -05:00
will.anderson c93be6a315 feat(recall): context-format
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 13m54s
2026-06-22 13:29:12 -05:00
will.anderson 53268c94b9 feat(recall): activation-seed 2026-06-22 13:29:12 -05:00
will.anderson 7e43a4ddc0 feat(recall): context-dedup 2026-06-22 13:29:12 -05:00
will.anderson e7669da325 feat(recall): session-start-recall 2026-06-22 13:29:12 -05:00
will.anderson 4f1286df05 feat(recall): cross-session-continuity 2026-06-22 13:29:12 -05:00
will.anderson 52c222c4f2 feat(recall): engram-scoring 2026-06-22 13:29:12 -05:00
will.anderson 0caccd0ea5 feat(recall): temporal-precision 2026-06-22 13:29:12 -05:00
will.anderson 03b5632fc1 feat(recall): recall-reliability 2026-06-22 13:29:12 -05:00
will.anderson 42bbadcd33 Merge pull request 'feat(recall): emotional-recall improvements' (#52) from improve/recall-emotional-recall into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 5m49s
feat(recall): emotional-recall improvements
2026-06-22 18:24:36 +00:00
will.anderson b6052f9de3 Merge pull request 'feat(recall): recall-completeness' (#48) from improve/recall-recall-completeness into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
feat(recall): recall-completeness improvements
2026-06-22 18:24:17 +00:00
will.anderson 1dd09b1980 feat(recall): context-format improvements
Neuron Soul CI / build (pull_request) Has been cancelled
- Add engram_render_node/render_nodes/dedup_nodes helpers for human-readable
  prose bullet output instead of raw JSON node objects reaching the LLM
- Fix engram_compile_ranked to use |N| index sentinel instead of _sel_N JSON
  mutation which leaked sentinel fields into LLM-visible node data (Issue #11)
- Update build_system_prompt with chat_mode param; no_tools_rule only included
  for chat path, not agentic paths (Issue #9)
- Move engram block to end of system prompt for strongest LLM attention (Issue #8)
- Label sections: STABLE IDENTITY vs RETRIEVED MEMORY (Issue #10)
- Render conversation history as User:/Assistant: dialogue instead of raw JSON
- Add RETRIEVED MEMORY labels to agentic and dharma room system prompt assembly
2026-06-22 13:20:19 -05:00
will.anderson 0113407728 feat(recall): emotional-recall improvements
Neuron Soul CI / build (pull_request) Has been cancelled
2026-06-22 13:17:12 -05:00
will.anderson be02fcd960 feat(recall): thread-aware activation seed for nlg soul path [issue 7]
Neuron Soul CI / build (pull_request) Successful in 4m37s
2026-06-22 13:17:04 -05:00
will.anderson cbe8c09068 feat(recall): context-dedup improvements
Neuron Soul CI / build (pull_request) Has been cancelled
- Cache bell node in engram_compile state (engram_compile_bell_node)
  so handle_chat reads cached value instead of duplicate bell query (Issue 2)
- Cache activation result (engram_compile_activation_json) for strengthen_chat_nodes
  reuse — eliminates third activation query per turn (Issue 7)
- Fix context cap to truncate at clean JSON object boundary (Issue 6)
2026-06-22 13:15:33 -05:00
will.anderson dfa2a33926 feat(recall): context-dedup improvements
- Cache bell node result in engram_compile state (engram_compile_bell_node)
  so handle_chat affective_prefix reads the cached value instead of firing
  a duplicate engram query for distress signals (Issue 2)

- Cache primary activation result in engram_compile state
  (engram_compile_activation_json) using nodes0 from engram_compile_multi

- Replace redundant engram_activate_json(message, 2) in strengthen_chat_nodes
  with state_get(engram_compile_activation_json) - eliminates a third
  activation query per turn (Issue 7)

- engram_compile already has object-boundary truncation and cross-set
  dedup via engram_nodes_merge/engram_dedup_nodes (Issues 1, 6, 9)
2026-06-22 13:12:08 -05:00
will.anderson 18e040acb1 feat(recall): recall-completeness improvements
Neuron Soul CI / build (pull_request) Has been cancelled
- Lower engram_compile_ranked threshold 25->15: include moderately-relevant older nodes
- Extend sentinel cleanup from _sel_9 to _sel_14 to prevent JSON noise
- Add engram_split_topics for multi-topic decomposition (AND/and/also/plus)
- Add engram_extract_entities for named entity dedicated searches
- Add engram_detect_recall_intent for boosted 40-candidate search on recall phrases
- Add engram_is_continuation replacing brittle 50-char threshold (now 80 + pronoun/opener detection)
- Add engram_compile_multi with depth 8 (was 5) and 30-candidate search pool
- Add engram_nodes_merge for clean two-array deduplication
- Replace engram_compile with multi-topic/entity/recall-boost version; budget 6000->8000
- Safe JSON truncation: scan for last } before budget cap instead of raw str_slice
- handle_chat and agentic_chat: use engram_is_continuation; thread snip 150->250
- session_preload: add project-status and session-summary search queries
2026-06-22 13:11:06 -05:00
will.anderson 3f53b6b1b6 feat(recall): session-start-recall improvements
Neuron Soul CI / build (pull_request) Has been cancelled
10 targeted fixes for session-start memory recall quality:

Issue 1: typed engram queries (Persona, WorkItem) replace generic keyword bags
Issue 2: bullet truncation raised from 120 to 350 chars
Issue 3: bullet caps raised to 8/6 with while-loop (no hardcoded unrolling)
Issue 4: read pre-computed soul_affective_context state key instead of duplicating boot-time search
Issue 5: last-session-topic node written per session; continuity section added to session_preload
Issue 6: greeting detection injects SESSION START orientation directive when continuity found
Issue 7: pinned identity node fallback when all engram searches return empty
Issue 8: session_preload always fires on first message (greeting detection controls directive only)
Issue 9: agentic path gets matching session_preload block (was missing entirely)
Issue 10: BellEvent recency reads created_at / embedded ts marker, not the never-written "ts" field
2026-06-22 13:06:55 -05:00
will.anderson 21f248a33a feat(recall): recall-completeness improvements
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
- Lower engram_compile_ranked threshold 25->15: include moderately-relevant older nodes
- Extend sentinel cleanup from _sel_9 to _sel_14 to prevent JSON noise
- Add engram_split_topics for multi-topic decomposition (AND/and/also/plus)
- Add engram_extract_entities for named entity dedicated searches
- Add engram_detect_recall_intent for boosted 40-candidate search on recall phrases
- Add engram_is_continuation replacing brittle 50-char threshold (now 80 + pronoun/opener detection)
- Add engram_compile_multi with depth 8 (was 5) and 30-candidate search pool
- Add engram_nodes_merge for clean two-array deduplication
- Replace engram_compile with multi-topic/entity/recall-boost version; budget 6000->8000
- Safe JSON truncation: scan for last } before budget cap instead of raw str_slice
- handle_chat and agentic_chat: use engram_is_continuation; thread snip 150->250
- session_preload: add project-status and session-summary search queries
2026-06-22 13:05:28 -05:00
will.anderson 795b32ad1a feat(recall): cross-session-continuity improvements
Neuron Soul CI / build (pull_request) Failing after 14m49s
2026-06-22 13:00:17 -05:00
will.anderson f33cdaf793 feat(recall): activation-seed improvements
- Issue 2: replace raw 50-char threshold with is_genuine_continuation() that
  checks for explicit follow-up phrases and mid-sentence capitalization (proper
  nouns signal a new topic, not a continuation)
- Issue 3/8: build_activation_seed() scans back to find the prior USER turn as
  the topic anchor instead of using the last assistant reply (hist_len-1)
- Issue 4: engram_compile_multi() fans out across three seeds — enriched primary,
  raw message (entity queries), and emotion query — merging non-redundant results
- Issue 5: agent workspace_root appended to ag_seed so agentic activation is
  workspace-aware; previously ignored despite being available in state
- Issue 6: distill_transcript() extracts salient tail+question content from full
  transcripts before passing to engram_compile in dharma room handlers
- Issue 7: dist/soul-with-nlg.el handle_chat and handle_chat_agentic now load
  history and use build_activation_seed() — the raw message path is eliminated
- Issue 9: topic_snip_from_entry() takes the TAIL 200 chars of a long reply and
  finds the last sentence boundary — captures end-of-reply named concepts
- Issue 10: multi_turn_topic() pulls up to 3 prior user turns into the non-
  continuation seed so earlier thread context re-activates high-salience nodes
2026-06-22 12:55:33 -05:00
will.anderson a60b1967df feat(recall): recall-completeness improvements
- Multi-query decomposition: split on AND/also/plus for multi-topic messages
- Named entity extraction: dedicated per-entity searches for project names
- Recall intent detection: boosted search pool for explicit recall requests
- Expanded pools: activation depth 8 (was 5), search 30->12 ranked (was 20->8)
- Threshold 25->15: retain moderately-relevant older nodes
- Sentinel cleanup extended to c14 for larger node pools
- Safe JSON truncation: find last closing brace before budget cap (8000 chars)
- Semantic continuation: engram_is_continuation replaces brittle 50-char threshold
- Thread snip: 150->250 chars for better pronoun resolution context
- Session preload: add project-specific and session-summary searches
2026-06-22 12:54:36 -05:00
will.anderson aef687b57c fix(reliability): state management
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
2026-06-22 12:54:32 -05:00
will.anderson 76c2e47d0f feat(recall): fix engram-scoring — float parsing, recency, threshold, sentinels
Neuron Soul CI / build (pull_request) Has been cancelled
Fix critical float parsing bug: %g serializes 0.70 as '0.7', naive str_replace
dot-strip gives str_to_int('07')=7 not 70. New parse_salience_100() uses
str_index_of to detect single-decimal strings and multiplies by 10 to correct.
Affects conv nodes (0.6/0.7), default memories (0.5/0.5), utterance nodes (0.6)
— the majority of the graph was scoring near zero and filtered by threshold=25.

Fix recency to use max(created_at, updated_at, last_activated) so nodes
strengthened by engram_strengthen() after chat turns score as fresh, not by
original write time. A node referenced yesterday but created 25 days ago
was borderline-filtered; now correctly scores fresh.

Compress recency dynamic range from 10x (10-100) to 1.54x (65-100) via
formula (50 + recency/2). Old formula: sal*imp*recency/10000 let recency
dominate — a canonical high-importance node at 30 days scored identical to
a fresh noise node. New: high-importance nodes remain competitive when old.

Add tier-aware decay with softer floor (30 not 10): Canonical nodes decay
over 365 days, Episodic over 90 days, working/untiered over 35 days. Long-
term identity and persona nodes are no longer permanently filtered.

Lower threshold from 25 to 15 to admit moderately-relevant older nodes that
pass scoring with the corrected formula. Backfills recall coverage lost when
single-decimal nodes were being silently discarded.

Apply scoring to activation nodes: engram_compile_ranked(activate_json, 5)
replaces unconditional pass-through. Threshold 5 preserves recall while
excluding genuinely zero-quality stale nodes.

Extend sentinel cleanup in engram_compile_ranked from _sel_0-9 to _sel_0-19
so max_nodes can safely be increased past 10 without JSON corruption.
2026-06-22 12:53:35 -05:00
will.anderson 0ede112d05 feat(recall): temporal-precision improvements
Neuron Soul CI / build (pull_request) Has been cancelled
Fix critical float parsing bug in engram_score_node: str_replace('.','')
then str_to_int silently miscored single-decimal salience strings (0.9->9,
0.7->7, 1.0->1). Introduce parse_salience_100() which detects decimal
position and scales correctly (no decimal: *100; one decimal: *10;
two decimals: as-is).

Replace flat 30-day linear decay with tier-aware decay curves: Canonical
nodes use a 365-day window (foundational identity resists aging), Episodic
nodes use 90 days, Working/untiered keep the existing 30-day slope. Floor
stays at 10 for all tiers.

Use max(created_at, updated_at) as the recency reference so revised nodes
are not penalised for their original creation date.

Extend affective context windows from 72h/7d to 14 days across all three
paths (engram_compile, handle_chat, soul.el load_identity_context) so a
Friday crisis carries into Monday sessions and all paths present consistent
context. The 72h/7d split caused conflicting affective context between
soul.el (which loaded a 5-day-old crisis node) and chat.el (which excluded
it on subsequent turns).

Add salience evolution to mem_consolidate: strengthen top working-memory
nodes (recently recalled across sessions) and Canonical-tier nodes
(foundational identity must not decay to the floor). Previously consolidate
returned structural counts only with no salience changes.

Expand conversation window from 20 to 40 turns in both handle_chat and the
agentic history trim. Long technical sessions were losing early problem
framing at 10 user + 10 assistant pairs.
2026-06-22 12:53:29 -05:00
will.anderson a39998a502 feat(recall): recall-reliability improvements
Neuron Soul CI / build (pull_request) Failing after 12m52s
- Q1: engram_numeric_valid() guard against non-numeric timestamps in bell scoring
- Q2: soul-agnostic cold-start fallback in engram_compile (drops genesis-specific hardcoded node IDs)
- Q3: partial-write guard and failure logging in conv_history_persist/load
- Q4: document circuit-breaker limitation requiring C runtime support
- Q5: println warnings on empty activation/search paths
- Q6: load_identity_context warns when all identity fetches return empty
- Q7: recall_status state tracking (ok/empty/unavailable) surfaced to LLM via MEMORY STATUS block
- Q8: document shared-state race conditions in engram_recall_status and safety_system_addendum
- CRITICAL BUG: conv_node_id empty check moved outside is_bell block so silent Conversation node loss is always logged
2026-06-22 12:52:31 -05:00
will.anderson 6edf9937dd fix(reliability): LLM retry
Neuron Soul CI / build (pull_request) Has been cancelled
2026-06-22 12:37:29 -05:00
will.anderson e447a87a00 fix(reliability): route error recovery 2026-06-22 12:37:21 -05:00
will.anderson 575ff1329a fix(reliability): engram connection 2026-06-22 12:34:04 -05:00
will.anderson db33b0cb91 fix(reliability): engram write 2026-06-22 12:32:59 -05:00
will.anderson f35569d4bb fix(reliability): cross-session affective state 2026-06-22 12:31:09 -05:00
will.anderson 94b71b6e6b fix(reliability): conversation history 2026-06-22 12:29:23 -05:00
will.anderson 392d2416ec fix(reliability): replace undefined session_exists with session_get check
Neuron Soul CI / build (pull_request) Failing after 13m25s
2026-06-22 12:21:31 -05:00
will.anderson 87c7d15b67 Merge pull request 'fix(reliability): session-boundary' (#41) from improve/reliability-session-boundary into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
fix(reliability): session-boundary — lifecycle guards, cleanup on expiry
2026-06-22 17:20:33 +00:00
will.anderson 93bed793c0 Merge pull request 'fix(reliability): safety-resilience' (#39) from improve/reliability-safety-resilience into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
fix(reliability): safety-resilience — crisis detection degradation paths
2026-06-22 17:19:29 +00:00
will.anderson 936b3f0ac9 feat(agentic): workspace root from request body — edit_file scope, trailing-slash fix, conditional state_set
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
Merge propose/agent-workspace-root-read (Tim's PR #28):
- path_within_root now appends '/' to root before prefix check (closes proj_evil bypass)
- edit_file in dispatch_tool now checks agent_workspace_root() and resolves path
- handle_chat_agentic reads agent_workspace_root from request body, only persists if non-empty
- Safety screen preserved after workspace root read (conflict resolved)
2026-06-22 12:16:28 -05:00
will.anderson 45dc80230d fix(safety): crisis detection — augment wired to system prompt, ts fallback, cross-session affective
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 6m10s
Merge improve/safety-crisis-detection (PR #31): reads layered_cycle_safety_system_addendum
from state and appends to system prompt on each turn (cleared after use to prevent bleed).
Safety ts extraction falls back to updated_at. Affective prefix now wires into system build.
Conflict with PR #33 resolved: capability_rules and session_preload both preserved.
2026-06-22 12:15:50 -05:00
will.anderson 9ba86b8f80 Merge pull request 'feat(memory): emotional salience tagging and cross-session distress persistence' (#34) from improve/soul-memory-formation into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
feat(memory): emotional salience tagging, BellEvent ts fix, label uniqueness
2026-06-22 17:14:25 +00:00
will.anderson 360c15d7fe Merge pull request 'fix(routes): error handling, health endpoint, request validation, rate limiting' (#32) from improve/soul-routes-api into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
fix(routes): error handling, health endpoint, validation, rate limiting
2026-06-22 17:14:10 +00:00
will.anderson e6da638536 fix(reliability): state-management — document and partially fix concurrent state races
Neuron Soul CI / build (pull_request) Has been cancelled
Issues addressed:
- #2: Document session_index non-atomic RMW (engram node safe under new mutex)
- #3: Document conv_history global race in handle_chat (session path unaffected)
- #4: Scope session_continuity state key per session_id in layered_cycle
- #5: Document active_imprint_id global race with fix path
- #6: Fix next_bridge_id to use uuid_v4() for collision-free IDs
- #7: Document session_hist_save delete-then-insert race
- #8: Document /api/graph/edges engram_save race (fixed in el_runtime.c)
- #10: Document agentic_conv_history global race in awareness loop

Issues #1 (engram_global mutex) and #8 (atomic engram_save write-to-temp+rename)
are fully fixed in el_runtime.c (committed to foundation/el repo separately).
Issue #9 skipped — already fixed in PR #31.
2026-06-22 12:12:58 -05:00
will.anderson 0c5b966773 fix(chat): fix auto_persist timestamp extraction and bell label uniqueness
Neuron Soul CI / build (pull_request) Has been cancelled
- engram_compile: BellEvent nodes do not carry created_at in the engram
  node JSON; extract the unix timestamp from the embedded ' | ts:NNNNN'
  pattern in the content string instead. Fall back to created_at/updated_at
  if the marker is absent. Guard str_to_int against empty string so the 72h
  recency check never silently treats every node as epoch-0 stale.

- auto_persist: append the current unix timestamp to the BellEvent label
  ('bell:soft:1749876543') to make it unique per turn. The previous label
  ('bell:soft') was the same for every soft bell, causing engram to treat
  all subsequent writes as updates to the same node.
2026-06-22 12:09:00 -05:00
will.anderson c87a536da3 fix(safety): wire safety augment into system prompt, fix timestamp fallback
Neuron Soul CI / build (pull_request) Has been cancelled
- Remove dead soft_bell block in layered_cycle that wrote soul_safety_system_augment
  to state but was never read; safety augmentation now goes through the correct
  layered_cycle_safety_system_addendum state key read by build_system_prompt
- build_system_prompt now reads layered_cycle_safety_system_addendum and appends
  it to the system prompt, clearing the key after consumption
- Timestamp extraction for distress nodes falls back to updated_at when created_at
  is empty, preventing the 72h recency check from always treating nodes as stale
2026-06-22 12:07:18 -05:00
will.anderson 2865d6ad26 fix(reliability): route-error-recovery
Neuron Soul CI / build (pull_request) Has been cancelled
- Issue #3: err_404/err_405 now emit HTTP 404/405 via __status__ envelope instead of HTTP 200
- Issue #4: add auth_check() function to handle_request; enforces NEURON_TOKEN on all routes except /health and /lineage
- Issue #5: missing required params now return HTTP 400 (__status__ envelope) in /api/chat (GET+POST), /imprint/contextual, /imprint/user, and handle_chat
- Issue #6: LLM unavailable in handle_chat now returns HTTP 503 instead of HTTP 200
- Issue #7: add 32 KB message size guard on POST /api/chat before engram_compile and LLM
- Issue #8: add TODO comment to route_health documenting the live-engram-query problem and the /health/deep split plan
- Issue #9: add comment to hist_trim documenting fragile str_index_of parser and silent data corruption risk
- Issue #10: add TODO comment in handle_request documenting missing per-IP rate limiting
- Issue #11: fix connectd_post temp file collision — add monotonic sequence counter so concurrent requests get unique paths
- Issue #12: fix call_mcp_bridge fixed temp file race — add monotonic sequence counter for unique paths under concurrent load
- Issues #1/#2: add TODO comment in handle_request documenting EL no-exception limitation and SIGSEGV handler gap
2026-06-22 12:00:06 -05:00
will.anderson 47d0e6f985 fix(reliability): llm-retry — empty response detection, configurable max_tokens, connector timeout
Neuron Soul CI / build (pull_request) Failing after 11m16s
Issue #5: detect empty string from llm_extract_text() as an error in handle_chat,
handle_chat_as_soul, and handle_dharma_room_turn. The C runtime silently returns ""
when the LLM response content array is missing or all blocks fail to parse; without
this guard the empty string passes through to callers as a silent empty reply.

Issue #9: make agentic_loop max_tokens configurable via NEURON_LLM_MAX_TOKENS env
var (default 4096). The hardcoded value is marginal for long tool chains (8 iterations
x 4096 tokens); operators can now set 8192+ for complex multi-step tasks without
rebuilding. Non-agentic path (llm_call_system) still uses the C runtime hardcode —
that fix lives in el_runtime.c (see TODO block added in this commit).

Issue #10: increase connector_tools_json and tool_auto_approved curl --max-time from
2s to 5s to reduce false-empty tool lists when neuron-connectd is under transient
load. Graceful degradation to [] on bridge down is unchanged.

Issues #1/#2/#3/#4/#6/#8: documented as TODO comments in chat.el. These require
targeted C runtime changes in el_runtime.c (llm_provider_request retry loop,
EL_LLM_TIMEOUT_MS separation, HTTP 429 backoff, 5xx retry, EL_HTTP_MAX_RESPONSE_BYTES
cap). Architectural decisions recorded so they are traceable to root causes.
2026-06-22 11:59:43 -05:00
will.anderson f0545defdb fix(reliability): session-boundary — ghost sessions, bridge leak, session validation
Neuron Soul CI / build (pull_request) Has been cancelled
- sessions.el: add session_exists() for chat-path session validation (ISSUE #6/#7)
- sessions.el: add session_create_cleanup() for ghost-session rollback (ISSUE #1)
- sessions.el: set session_pending_first_msg flag in session_create; clear it in
  session_hist_save so the first successful chat marks the session active (ISSUE #1)
- sessions.el: session_delete now clears mcp_bridge:<id> and always_allow_<id>
  state keys so abandoned pending-tool sessions do not accumulate (ISSUE #5)
- sessions.el: add TODO comments for ISSUE #2 (no TTL/expiry), ISSUE #3
  (non-atomic delete-then-create), ISSUE #4 (no concurrent-create guard),
  and ISSUE #8 (reconnect/duplicate resume race) where fixes are too invasive
  to land without new runtime primitives
- chat.el: validate session_id exists via session_exists() before entering
  agentic_loop; unknown session_ids now return a 404-style error instead of
  silently starting a fresh empty session (ISSUE #6/#7)
2026-06-22 11:58:33 -05:00
will.anderson ae9a139440 fix(reliability): safety-resilience — bell augmentation, safe mode, dedup logging, tab escaping, handle_chat coverage
Neuron Soul CI / build (pull_request) Has been cancelled
2026-06-22 11:57:43 -05:00
will.anderson d008649c3e fix(reliability): engram-connection
Neuron Soul CI / build (pull_request) Has been cancelled
- entrypoint.sh: extend engram health-check timeout 30->60s; set
  EL_HTTP_TIMEOUT_MS=10000 and EL_HTTP_CONNECT_TIMEOUT_MS=3000 to bound
  awareness loop blocking window to 10s/call (down from 60s default)
- soul.el: 3-attempt retry loop for boot-time /api/nodes+/api/edges fetch;
  validate non-empty JSON array before loading to prevent silent zero-node
  identity graph from transient post-healthcheck network hiccup
- awareness.el: soft circuit-breaker in ise_post (opens after 3 failures,
  30s backoff, half-open probe); /api/sync refresh skips HTTP call when
  breaker is open; error-JSON detection on sync response

TODOs: full async dispatch, connection pooling (require EL futures/persistent curl)
2026-06-22 11:57:20 -05:00
will.anderson aa70c5dde6 fix(reliability): safety-resilience — bell augmentation, safe mode, dedup logging, tab escaping, handle_chat coverage 2026-06-22 11:54:40 -05:00
will.anderson b7fd8901d4 fix(routes): fix handle_request ABI, 429 status code, soul_boot_ts write
Neuron Soul CI / build (pull_request) Has been cancelled
2026-06-22 11:53:09 -05:00
will.anderson deddb9a18e fix(reliability): safety-resilience — bell augmentation, safe mode, dedup logging, tab escaping, handle_chat coverage 2026-06-22 11:53:07 -05:00
will.anderson 494d973a3b fix(reliability): engram-write — guard all fire-and-forget writes
Neuron Soul CI / build (pull_request) Has been cancelled
Every engram_node_full call that dropped its return value now binds it
and emits a println on empty string. engram_save calls in consolidate,
heartbeat, and dharma-room-turn are checked for failure. The two API
handlers (log_state_event, tune_config) that skipped api_persisted()
now match the read-back-after-write contract used everywhere else in
neuron-api.el.

Files changed:
- chat.el: conv_history_persist, handle_dharma_room_turn, auto_persist
- soul.el: emit_session_start_event, seed_persona_from_env HTTP check
- memory.el: mem_save, mem_boot_count_inc
- neuron-api.el: handle_api_log_state_event, handle_api_tune_config,
  handle_api_consolidate (engram_save + session summary write)
- awareness.el: ise_post local-engram fallback path

TODO comments added for non-atomic patterns (issues #12, #13) and
the missing circuit breaker (#14) — these require new primitives.
2026-06-22 11:48:59 -05:00
will.anderson 34551695a1 fix(reliability): cross-session-affective
Neuron Soul CI / build (pull_request) Has been cancelled
- Fix state key mismatch: soul.el layered_cycle now reads conv_history
  (not conversation_history), unblocking the safety_score_distress_history
  history-amplification path in safety_threat_score
- Add safety_augment_system call on the main handle_chat path so the
  phrase-list bell detector fires on all chat turns, not just dharma rooms
- Add cross-session affective engram query in load_identity_context() at
  boot; stores distress/crisis signals from prior sessions under
  soul_affective_context with a 7-day soft recency filter
2026-06-22 11:48:30 -05:00
will.anderson dcf050ee3c fix(agentic): workspace root security — edit_file scoping, trailing-slash normalization, conditional state_set
Neuron Soul CI / build (pull_request) Has been cancelled
2026-06-22 11:46:44 -05:00
will.anderson 615f0cee08 fix(reliability): conv-history — asymmetric load, silent failures, broken trim, agentic gap
Neuron Soul CI / build (pull_request) Has been cancelled
Issues addressed:
- #1 ASYMMETRIC PERSIST/LOAD: conv_history_load() now tries engram_get_node_by_label()
  first (symmetric with the label-based write), falling back to vector search only when
  label lookup returns nothing. Immune to cold/corrupt vector index.
- #2 SILENT LOAD FAILURE: all failure paths in conv_history_load() and conv_history_persist()
  now emit a println log line rather than silently returning "" or dropping writes.
- #3 NO RECOVERY PATH: documented as TODO with explanation of why a full recovery path
  (retry, ID fallback, orphan cleanup) is too invasive for a targeted fix here.
- #4 OVERWRITE WITHOUT DELETE: documented with TODO to replace engram_node_full with
  explicit delete-then-create once engram exposes a label-scoped delete API.
- #5/#10 BROKEN TRIM / OFF-BY-ONE: hist_trim() rewritten to use json_array_len /
  json_array_get (structural JSON ops) instead of raw str_index_of scanning for
  '{"role":' markers. Immune to marker strings appearing inside message content.
  Minimum retained count guard added: never trims below 2 entries.
- #6 PARTIAL-WRITE GUARD: conv_history_persist() refuses to write a blob that doesn't
  contain both '[' and ']'. conv_history_load() requires both before accepting content.
- #7 DUAL STORAGE: documented with a comment at the persist call site.
- #8 NO MAX SIZE GUARD: documented as TODO with rationale for why a byte-length cap
  requires a more invasive change (entry truncation or summarisation).
- #9 AGENTIC HISTORY NOT PERSISTED: handle_chat_agentic() now calls conv_history_persist()
  for the default global session (hist_key == "conv_history") after updating state,
  matching the non-agentic path's durability. Named sessions remain in-process only.
2026-06-22 11:46:00 -05:00
will.anderson 260b9e55d4 feat(soul): context quality, profile load, refusal handling
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 9m48s
2026-06-22 11:39:33 -05:00
will.anderson fda76ae05b Merge pull request 'feat(ci): strip debug symbols from soul binary before publishing' (#35) from improve/soul-strip into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
2026-06-22 16:39:14 +00:00
will.anderson d3eda47fd3 feat(ci): strip debug symbols from soul binary before publishing
Neuron Soul CI / build (pull_request) Has been cancelled
Add strip -s after gcc compilation to remove symbol table and relocation info.
Reduces binary size and prevents symbol-level reverse engineering of EL runtime internals.
2026-06-22 11:37:28 -05:00
will.anderson f3069b481d Merge pull request 'fix(chat): forbid fake tool calls in tool-less (Just chat) mode' (#29) from propose/no-fake-tools-in-chat-mode into main
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Has been cancelled
fix(chat): forbid fake tool calls in tool-less mode
2026-06-22 16:36:43 +00:00
will.anderson b2008f4894 feat(memory): emotional salience tagging and cross-session distress persistence
Neuron Soul CI / build (pull_request) Successful in 5m36s
- auto_persist: detect bell level (soft/hard) on every user message using
  safety_detect_bell_level; write a dedicated BellEvent engram node with
  calibrated salience alongside the Conversation node when a bell fires.
  Tag the Conversation node with bell:soft/bell:hard and 'affective' for
  direct discovery without scanning all chat nodes.

- auto_persist: track per-session bell count, dominant level, and last
  signal in state (session_bell_count/level/signal keys) so downstream
  functions can act on the emotional history without re-scanning engram.

- engram_compile: include the top-1 most recent BellEvent node within 72h
  in every context build. Distress context from earlier turns (same or
  recent session) automatically travels into all subsequent LLM calls.

- hist_trim_with_bell_guard: replace hist_trim at the handle_chat call site.
  Before evicting the oldest turn from the 20-turn window, inspect the user
  message for bell signals. If a bell was present, write a preservation
  BellEvent to engram before dropping the turn so the full message survives
  the rolling window.

- session_hist_save: after writing the history node, check session bell
  counters. On the first save where bell_count > 0, write a
  session:emotional-summary BellEvent node with distress signal, count,
  and dominant level. A state flag prevents duplicate writes on subsequent
  saves in the same session.
2026-06-22 11:23:15 -05:00
will.anderson 28fce08dd9 feat(soul): context quality, first-message profile load, refusal handling, agentic safety
Neuron Soul CI / build (pull_request) Has been cancelled
- engram_compile: rank search results by recency x relevance before including
  in context. Pulls 20 candidates, scores each (salience * importance * recency
  decay), keeps top 8. Eliminates stale/low-signal nodes that diluted context.

- handle_chat: on hist_len==0 (session start), proactively load user profile
  and active-work context from engram and inject as brief bullets in the system
  prompt. Gives the soul grounding before any conversation history exists.

- build_system_prompt: add [CAPABILITY GAPS] directive instructing the soul to
  offer partial help and reasoning instead of flat "I don't have access to that"
  refusals when a tool is missing.

- handle_chat_agentic: run safety_screen at entry, mirroring layered_cycle.
  Hard bell exits immediately with the crisis response without entering the loop.

- agentic_loop: surface the 8-iteration cap explicitly in the error envelope
  ("agentic loop hit the 8-iteration cap...") rather than the opaque "no response".
  Add iterations count to both the error and success envelopes for observability.
2026-06-22 11:22:14 -05:00
will.anderson d92b8c279a fix(routes): error handling, health diagnostics, request validation, rate limiting
Neuron Soul CI / build (pull_request) Has been cancelled
- Add per-IP in-memory rate limiter (60 req/min default, configurable via
  soul_rate_limit state key; /health exempt; loopback callers skipped)
- Extend /health with uptime_secs (from soul_boot_ts) and live LLM probe
- Add missing_param 400 guard on POST /api/chat before passing to LLM
- Standardise error envelopes: add "code" field to err_404/err_405 and all
  missing-param returns; route_synthesize now errors clearly instead of
  returning the misleading {"mechanism":"did not engage"} on bad input
- Document streaming gap in /api/chat (SSE not implemented, note added)
- handle_request gains ip param; rate_limit_check wired at entry point
2026-06-22 11:21:18 -05:00
will.anderson e9a8a659e0 fix(safety): crisis detection — 4 targeted fixes
Neuron Soul CI / build (pull_request) Failing after 14m43s
- soul.el: fix state key bug in layered_cycle (conversation_history -> conv_history)
- safety.el: add indirect crisis location patterns to soft_bell phrase list
- soul.el: wire safety_augment_system into layered_cycle for soft_bell turns
- chat.el: load cross-session affective context at session start when distress signals found within 72h
2026-06-22 11:20:42 -05:00
Tim Lingo f6c4ea70a0 fix(chat): forbid fake tool calls in tool-less (Just chat) mode
Neuron Soul CI / build (pull_request) Successful in 4m47s
REPRODUCED: in the non-agentic path (Tools off / 'Just chat'), asking for
tool-work makes the model role-play tool use — it emits a fake ```json {...}```
'tool call' and says 'let me search/query/pull your sessions' while NOTHING
runs. Reads as a broken/lying app. (The agentic path is fine: verified it
calls search_memory and reports honestly.)

Root cause: build_system_prompt (handle_chat, the tool-less path) never told
the model it has no tools this turn, so it fabricated.

Fix: add a NO-TOOLS directive to the non-agentic system prompt — never emit
tool calls / JSON tool blocks / 'let me pull...' narration; answer from context
only; if a tool is truly needed, say so in one sentence and tell the user to
turn Tools on. Applied to chat.el (source) AND dist/soul.c (the curated TU the
CI compiles), so the CI-built binary carries it.

Verified the FABRICATION repro on the live local soul; could not verify the
patched binary locally (no matching el-runtime version on this machine — a
hand-link against origin/main runtime 404s on all routes). Builds correctly via
CI, which links soul.c against the pinned runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:57:24 -05:00
Tim Lingo 1b83b18c39 propose(agentic): read agent_workspace_root from request body and persist to state
Neuron Soul CI / build (pull_request) Successful in 7m45s
Completes the UI<->soul contract for #23 (scope file/command tools to an agent
workspace root). #23 made the tools read state_get("agent_workspace_root"), but
nothing set that key from the desktop UI, so the agent panel's Workspace Folder
was cosmetic and tools ran unscoped (default-allow). This reads the root the UI
now sends on each agentic request and state_sets it before tool dispatch, so
agent_workspace_root() picks it up for the turn.

Minimal + pattern-matching (same json_get/state_set shape used throughout chat.el).
Empty body field => unscoped (backward-compatible) and preserves the env fallback.

FOR WILL'S REVIEW — do not merge without sign-off:
- Ownership model: set state from body each turn (so clearing the folder un-scopes)
  vs. only-when-nonempty. Flagged inline.
- Pairs with neuron-ui PR #32 (ChatRequest.agentWorkspaceRoot).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:56:20 -05:00
will.anderson ddd858d2ec fix(deploy): extend rollout timeout to 8m for GKE Autopilot cold starts
Neuron Soul CI / build (push) Has been cancelled
Deploy Soul to GKE / deploy (push) Failing after 5m48s
2026-06-19 15:35:34 -05:00
will.anderson 996dd3860a fix: replace embedded python with sed in deploy-gke manifest update step
Neuron Soul CI / build (push) Successful in 7m6s
Deploy Soul to GKE / deploy (push) Failing after 8m11s
2026-06-19 15:25:22 -05:00
will.anderson 6f4adf7640 self-review 2026-06-19: filter auto_term to Memory/BacklogItem/Entity only
Knowledge nodes dominated the WM-autobiographical auto_term slot:
'Numeric tier strings...' (a Knowledge node) always scored highest
in WM and its first word 'Numeric' became the curiosity seed every
scan — activating more Numeric nodes, keeping that node in WM,
repeating indefinitely.

Fix: only derive auto_term from Memory, BacklogItem, or Entity nodes.
Knowledge nodes are reference material, not live context. Dynamic/
personal nodes carry the salience worth radiating from.

Also patches proactive_curiosity directly in dist/neuron.c (ELC
cannot compile soul.el within timeout — fallback build pattern).
2026-06-19 08:49:42 -05:00
will.anderson 7e901bbbd2 fix(ci): prune Docker state at start of CI build to prevent disk exhaustion
Neuron Soul CI / build (push) Successful in 5m22s
2026-06-18 15:03:19 -05:00
will.anderson 2de1e60b8a fix(ci): update infra manifests after blue-green swap
Neuron Soul CI / build (push) Failing after 10m28s
2026-06-18 14:23:30 -05:00
will.anderson b563fff062 fix(ci/docker): pre-download artifacts before build, remove --secret
Neuron Soul CI / build (push) Successful in 6m32s
Deploy Soul to GKE / deploy (push) Successful in 7m46s
The Dockerfile's --mount=type=secret path was corrupting the SA key JSON
due to control character handling differences. Pre-download soul + El SDK
in the CI workflow (using already-authenticated gcloud) and COPY them from
the build context. No credentials needed inside the Docker build.
2026-06-18 14:04:03 -05:00
will.anderson fdd946b3d4 fix(ci): serialize build+deploy via concurrency group to prevent Docker exhaustion
Neuron Soul CI / build (push) Failing after 10m13s
Deploy Soul to GKE / deploy (push) Failing after 5m25s
2026-06-18 13:43:52 -05:00
will.anderson de8f021a55 fix(ci): install docker-buildx-plugin for BuildKit secret support
Deploy Soul to GKE / deploy (push) Failing after 11m0s
Neuron Soul CI / build (push) Failing after 11m11s
2026-06-18 13:42:56 -05:00
will.anderson d0c4d19faa fix(ci): prune Docker state before deploy to recover disk space
Deploy Soul to GKE / deploy (push) Failing after 12m57s
Neuron Soul CI / build (push) Failing after 13m7s
Previous builds leave cached layers and images on the runner. Add a
docker system prune at start of deploy to avoid container-creation
failures from disk exhaustion.
2026-06-18 13:15:52 -05:00
will.anderson b715a5dffb fix(ci): enable DOCKER_BUILDKIT and fix SHA extraction in deploy
Deploy Soul to GKE / deploy (push) Failing after 11m23s
Neuron Soul CI / build (push) Failing after 11m33s
--secret requires BuildKit; DOCKER_BUILDKIT=1 enables it on the legacy
Docker client. Also add GITHUB_SHA fallback and git rev-parse last-resort
so the image tag is never empty.
2026-06-18 12:42:25 -05:00
will.anderson 28e0afc11d fix(ci): preserve pre-compiled soul.c across elb run
Deploy Soul to GKE / deploy (push) Failing after 5m36s
Neuron Soul CI / build (push) Successful in 6m24s
elb overwrites dist/soul.c with a fresh (non-inlined) compilation before
its link step fails, discarding the patched self-contained version.
Save the repo copy before elb and restore it after so the compiler always
gets the complete translation unit with all patches applied.
2026-06-18 12:34:06 -05:00
will.anderson 46a7a4e9d8 Set USE_GKE_GCLOUD_AUTH_PLUGIN for GKE deploy workflow
Neuron Soul CI / build (push) Failing after 5m18s
Deploy Soul to GKE / deploy (push) Failing after 10m13s
Modern gcloud CLI (>= 400) requires this env var so kubectl uses the
installed gke-gcloud-auth-plugin binary instead of the deprecated
application-default credentials path. Without it, kubectl commands
silently fail even after get-credentials succeeds.
2026-06-18 12:23:49 -05:00
will.anderson ceef82464a chore(dist): update pre-compiled soul.c to patched4
Deploy Soul to GKE / deploy (push) Failing after 6m20s
Neuron Soul CI / build (push) Failing after 6m56s
Incorporates PRs #22/#23/#24:
- agentic_tools_all dedup fix (no duplicate web_search tool)
- workspace scope functions (agent_workspace_root, path_within_root, resolve_in_root)
- updated dispatch_tool with workspace confinement
- canonical-self bridge (ensure_self_canonical_bridge)

Also incorporates CI link fix from PR #26 (soul.c is self-contained, no
other dist/*.c needed). Fixes the CI build step which was compiling the
old June-16 soul.c that predated all these changes.
2026-06-18 12:19:54 -05:00
will.anderson 6f113a9601 Merge pull request 'feat(agentic): scope file/command tools to an agent workspace root' (#23) from feat/agent-tool-workspace-scope into main
Neuron Soul CI / build (push) Failing after 6m47s
Deploy Soul to GKE / deploy (push) Failing after 5m21s
2026-06-18 16:29:35 +00:00
will.anderson 8e25da3673 Merge pull request 'fix(identity): bridge public self anchor to the curated self node' (#24) from fix/canonical-self-bridge into main
Deploy Soul to GKE / deploy (push) Failing after 8m15s
Neuron Soul CI / build (push) Failing after 14m56s
2026-06-18 16:29:16 +00:00
will.anderson ca29e7ca35 Merge pull request 'fix(ci): link soul.c only — fixes capability #error breaking every build' (#26) from fix/ci-soul-build-single-file into main
Neuron Soul CI / build (push) Failing after 9m27s
Deploy Soul to GKE / deploy (push) Failing after 10m3s
fix(ci): link soul.c only — fixes capability #error breaking every build
2026-06-18 16:29:05 +00:00
will.anderson 6576dddca2 Merge pull request 'fix(chat): remove duplicate web_search tool crashing all agentic requests' (#22) from fix/agentic-tools-duplicate-web-search into main
Deploy Soul to GKE / deploy (push) Failing after 8m40s
Neuron Soul CI / build (push) Failing after 10m21s
fix(chat): remove duplicate web_search tool crashing all agentic requests
2026-06-18 16:28:41 +00:00
will.anderson ce3c3873c5 fix(ci): link soul.c only — drop multi-module cc that triggers capability #error
Neuron Soul CI / build (pull_request) Failing after 7m44s
elb generates a dist/soul.c with all El modules inlined. Linking
dist/soul.c alone is sufficient and is exactly what the local mac
build does. Including other dist/*.c files causes two failures:
  1. dist/chat.c has a capability-violation #error that fires when the
     file is compiled as a utility module (outside the cgi entrypoint).
  2. --allow-multiple-definition masked other issues silently.

Drop OTHER_C, drop --allow-multiple-definition, drop the now-unused
elp-c-decls.h generation step. The cc command now matches the proven
local build exactly.
2026-06-18 11:27:57 -05:00
Tim Lingo 149a042db9 fix(identity): bridge public self anchor to the curated self node
Neuron Soul CI / build (pull_request) Failing after 4m34s
The graph API resolves name=self/neuron to kn-efeb4a5b (neuron-api.el:471),
which carries only 8 incidental 'tagged' edges. The curated identity lives on
self node 015644f5 (1461 edges: identity, embodies, remembers, values). So
public self-traversal reaches tags, not the real self.

Add ensure_self_canonical_bridge(): an idempotent boot-time repair that links
kn-efeb4a5b <-> 015644f5 with a 'canonical-self' edge, only if missing. Runs in
the genesis safe-to-seed path regardless of the <100-edge gate, so the live
populated graph gets repaired and persisted. Connect-only-if-missing prevents
the duplicate-edge stacking that gates init_soul_edges().

Compile-checked with elc (darwin arm64); not link/run-gated locally. Needs a
soul build + smoke test before merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 23:53:13 -05:00
Tim Lingo 071c0eeb9f feat(agentic): scope file/command tools to an agent workspace root
Neuron Soul CI / build (pull_request) Failing after 5m7s
Confine the agentic file tools (read_file, write_file, list_files, grep)
to a configured workspace subtree via a lexical path check, and run
run_command with its cwd set to that root. Root comes from state key
"agent_workspace_root" or env NEURON_AGENT_ROOT. When no root is set,
behavior is unchanged (unscoped) for backward compatibility.

Defense-in-depth, NOT a hard boundary: the lexical guard does not resolve
symlinks and cannot stop an arbitrary shell command from cd-ing out of the
root. Real confinement needs runtime support (cwd-locked exec / sandbox-exec
/ chroot) in el_runtime.c.

Compile-checked with elc (darwin arm64); not link/run-gated locally
(darwin elb unavailable). Needs a soul build + smoke test before merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 23:49:01 -05:00
will.anderson 53fb75353f fix(chat): remove duplicate web_search tool in agentic_tools_all
Neuron Soul CI / build (pull_request) Failing after 5m24s
agentic_tools_literal() already contains a custom web_search tool.
agentic_tools_with_web() adds the Anthropic server-side web_search_20250305
tool (also named web_search). Combining them caused Anthropic to reject
every agentic request with 'Tool names must be unique.'

agentic_tools_all() now calls agentic_tools_literal() directly. Connector
tools splice in as before. The web_search-only variant (agentic_tools_with_web)
is unchanged for callers that specifically want native search without connectors.
2026-06-17 14:11:50 -05:00
will.anderson 74ac457e1c Merge pull request 'fix(soul): ratio guard against genesis seeding over a populated engram' (#21) from feat/connectors-soul into main
Deploy Soul to GKE / deploy (push) Failing after 12m51s
Neuron Soul CI / build (push) Failing after 13m3s
fix(soul): ratio guard against genesis seeding over a populated engram
2026-06-17 18:19:52 +00:00
will.anderson 8b692e4666 fix/test: PR #21 review — guard, safety Bell, api write-back, temp paths
Neuron Soul CI / build (pull_request) Failing after 13m22s
fix(soul): add HTTP-engram guard to safe_to_seed — when ENGRAM_URL is set
the HTTP Engram owns persistence; genesis must never save to local snapshot
regardless of node counts (was: guard_disk forced to empty string, making
the ratio check vacuously true and allowing init_soul_edges+engram_save).

fix(soul): use multiplication form for ratio guard — node_count * 16000 <
disk_len avoids floor-division truncation that underestimated boundary files
(250KB / 16000 = 15.6, floors to 15; a 15-node graph wrongly passed old guard).

fix(chat): add safety_augment_system to handle_chat_as_soul,
handle_dharma_room_turn, and handle_dharma_room_turn_agentic — all three
called the LLM without Hard Bell evaluation, leaving users in dharma rooms
without crisis resource routing.

fix(neuron-api): add api_persisted read-back to handle_api_define_process —
was the only write handler that returned ok:true without verifying the node
was actually written to engram.

fix(routes): unique temp file path in connectd_post — replaces fixed
/tmp/neuron-connectors-req.json with a timestamped path to prevent
collision if concurrency is added or two soul instances share a machine.

test: add tests/test_bell_safety.el — covers safety_detect_bell_level
(none/soft/hard), safety_classify_hard_bell (abuse/self_harm routing),
safety_normalize (smart-quote), safety_augment_system, and
handle_safety_contact_post (validation + read-back).

test: add tests/test_soul_guard.el — pure-function logic tests for the
safe_to_seed predicate: 200KB boundary, 47MB/63-node clobber scenario,
HTTP-engram mode, multiplication vs division truncation at 250KB.

test: add tests/test_api_define_process.el — verifies the define_process
write is read-back verified after the fix.
2026-06-17 13:19:15 -05:00
Tim Lingo 5ddb860201 fix(soul): ratio guard against genesis seeding over a populated engram
Genesis boot previously seeded a fresh identity and saved it over snapshot.json
whenever the in-memory graph looked empty. Replace the fixed node-count threshold
with a ratio guard: refuse to seed when the on-disk snapshot is large
(>200KB) but the loaded graph is sparse (< disk/16000 nodes).

KNOWN LIMITATION: this gates only the seed/pre-serve-save path. The deeper cause
is a non-atomic engram_save (fopen wb truncates to 0 before writing 47MB), which
creates a window where a concurrent load reads an empty file -> genesis -> and if
guard_disk is read in that same window the guard passes. The real fix is an
atomic engram_save (temp + fsync + rename) in el_runtime.c, tracked separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 13:18:35 -05:00
Tim Lingo 6d8a992716 feat(soul): add safety module, expand connectors API, memory-recall bug notes
- safety.el/.elh: new safety module
- neuron-api.el, routes.el, soul.el, chat.el: connectors API expansion
- regenerated dist/ C artifacts
- MEMORY_RECALL_BUG.md: investigation notes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 13:18:35 -05:00
will.anderson 2797909633 Merge pull request 'fix(chat): prevent double-escape corruption of messages/tools in agentic bridge' (#20) from fix/bridge-save-serialization into main
Deploy Soul to GKE / deploy (push) Failing after 13m1s
Neuron Soul CI / build (push) Failing after 13m10s
fix(chat): prevent double-escape corruption of messages/tools in agentic bridge
2026-06-17 18:08:12 +00:00
will.anderson 8db3c8c7f7 fix(chat): harden bridge_save/agentic_resume against empty and corrupt state
Neuron Soul CI / build (pull_request) Failing after 13m18s
BLOCKER 1: use untyped reassignment (let x = ...) for the fallback bindings
in agentic_resume instead of re-declaring typed let bindings (let x: Type = ...)
for the same variable in the same scope. The typed form risks shadowing semantics
that differ from the established pattern used everywhere else in the loop
(e.g. agentic_loop line 720).

BLOCKER 2: add empty-string guards in both bridge_save and agentic_resume.
bridge_save now returns false without writing state if messages or tools_json
is empty — preventing syntactically invalid JSON blobs. agentic_resume now
returns an error envelope after the fallback resolution if either field is
still empty, rather than passing empty strings into agentic_loop which would
silently start a fresh turn with no context.

Also add tests:
- test_bridge_serialization.el: covers bridge_save empty-guard, golden-path
  raw-JSON round-trip, agentic_resume unknown/corrupt/missing-fields paths,
  and legacy string-escaped fallback path
- test_sessions_routes.el: covers DELETE and PATCH /api/sessions/:id routes
  (valid args, unknown id, empty body) and GET /api/sessions regression after
  removal of the duplicate route_sessions() handler
2026-06-17 13:07:43 -05:00
will.anderson e7297275a3 Merge pull request 'fix(chat): wire agentic_tools_all into both agentic loop entry points' (#19) from fix/agentic-tools-all into main
Deploy Soul to GKE / deploy (push) Failing after 6m23s
Neuron Soul CI / build (push) Failing after 14m16s
fix(chat): wire agentic_tools_all into both agentic loop entry points
2026-06-17 18:06:35 +00:00
will.anderson fc74bd2a4b Merge pull request 'fix(sessions): unify dual suspension systems, wire approve to agentic_resume' (#18) from fix/agentic-tool-approval-unification into main
Deploy Soul to GKE / deploy (push) Failing after 6m35s
Neuron Soul CI / build (push) Failing after 14m31s
fix(sessions): unify dual suspension systems, wire approve to agentic_resume
2026-06-17 18:06:01 +00:00
will.anderson 189093b348 Merge pull request 'fix(routes): remove duplicate GET /api/sessions shadowing session_list()' (#17) from fix/sessions-route-dedup into main
Deploy Soul to GKE / deploy (push) Failing after 14m41s
Neuron Soul CI / build (push) Failing after 14m51s
fix(routes): remove duplicate GET /api/sessions shadowing session_list()
2026-06-17 18:05:19 +00:00
will.anderson f7ae7df9d6 fix/test(chat): guard handle_dharma_room_turn_agentic against tool_pending and empty reply
Neuron Soul CI / build (pull_request) Failing after 8m0s
When agentic_loop suspends for an MCP bridge tool it returns a
{"tool_pending":true,...} envelope with no "reply" key. Without an
explicit check, json_get(loop_result, "reply") returns "" and the
function emitted {"response":"","cgi_id":"..."} — a silent empty
response indistinguishable from a successful LLM turn with no content.

Two guards added after the existing error check:

1. tool_pending passthrough: if the loop suspended, return the pending
   envelope directly so callers (dharma room orchestrators) can
   distinguish suspension from failure and route to the approve flow.

2. Empty-reply guard: if final_text is empty after the pending check,
   return an explicit {"error":"no response",...} envelope instead of
   silently succeeding with an empty response field.

Also adds tests/test_agentic_tools.el:
- agentic_tools_all() includes all literal tool names and web_search
- connector_tools_json() returns valid JSON when bridge is down (graceful degradation)
- tool_pending envelope detection patterns (the is_pending logic)
- json_get(pending_envelope, "reply") returns "" confirming the empty-reply
  guard is load-bearing (pure string/JSON, no LLM or network required)
2026-06-17 13:01:13 -05:00
will.anderson b1fdd14ed5 fix(sessions): invalidate session_index cache in session_delete
Neuron Soul CI / build (pull_request) Failing after 8m11s
session_delete cleared the per-session state (session_hist_ and
session_node_) but not the shared session_index cache. The next call
to session_list() hit the fast path (state_get("session_index")) and
returned the deleted session until the daemon restarted.

session_update_patch already called state_set("session_index","") to
force a re-fetch from Engram; session_delete now does the same.

Add tests/test_sessions.el covering:
- session_title_from_message (pure function, all edge cases)
- session_make_content (JSON structure and required session:meta marker)
- DELETE cache invalidation: session_index cleared, fast path disabled
- PATCH cache invalidation: stale title/folder not returned via fast path
- GET /api/sessions: session_list() fast path returns session_index
  (confirms removal of the stale route_sessions() engram stub)
2026-06-17 12:59:47 -05:00
will.anderson 91902d6bf2 fix(sessions): resolve blockers and warnings in handle_session_approve
Neuron Soul CI / build (pull_request) Failing after 9m3s
BLOCKER 1 (sessions.el, modern path): Add guard that rejects allow
action when tool_name is missing from the body. Previously, omitting
tool_name caused dispatch_tool("", ...) to return "unknown tool: " and
silently inject a corrupted tool_result into the conversation.

BLOCKER 2 (sessions.el, modern path): Stop re-executing client-side
tools server-side. When the client provides body["content"], use it
directly as the tool result (matching the handle_tool_result contract).
Only fall back to dispatch_tool for builtin tools when no content is
present. Non-builtin tools with no client content now return a clear
error instead of a broken dispatch attempt.

WARNING 1 (chat.el, agentic_loop): Wire always_allow_<session_id> state
into the bridge-suspension decision. When a tool is in the session's
always-allow list, treat it as locally dispatchable (like a builtin)
and skip the bridge pause, so the approval UI is never shown again for
that tool in that session.

WARNING 2 (sessions.el, legacy path): Read a "tools_variant" field from
the legacy pending blob when present, and call the corresponding
agentic_tools_*() variant on resume. Falls back to agentic_tools_literal()
for blobs written before this field existed.

tests/test_sessions_approve.el: Add 10-case test suite covering:
- empty session_id / missing call_id / missing action guards
- no pending tool returns correct error
- missing tool_name on allow returns error (BLOCKER 1)
- deny action does not require tool_name
- legacy call_id mismatch returns mismatch error
- always action records tool_name in always_allow state
- allow with client content skips re-execution (BLOCKER 2)
2026-06-17 12:58:44 -05:00
Tim Lingo c6d4530060 Merge remote-tracking branch 'origin/fix/sessions-route-dedup' into green/agentic-fixes
Neuron Soul CI / build (pull_request) Failing after 6m0s
2026-06-16 18:53:18 -05:00
Tim Lingo 98a0bfd09c Merge remote-tracking branch 'origin/fix/agentic-tools-all' into green/agentic-fixes 2026-06-16 18:53:18 -05:00
Tim Lingo bcdadb7323 fix(soul): ratio guard against genesis seeding over a populated engram
Neuron Soul CI / build (pull_request) Successful in 5m44s
Genesis boot previously seeded a fresh identity and saved it over snapshot.json
whenever the in-memory graph looked empty. Replace the fixed node-count threshold
with a ratio guard: refuse to seed when the on-disk snapshot is large
(>200KB) but the loaded graph is sparse (< disk/16000 nodes).

KNOWN LIMITATION: this gates only the seed/pre-serve-save path. The deeper cause
is a non-atomic engram_save (fopen wb truncates to 0 before writing 47MB), which
creates a window where a concurrent load reads an empty file -> genesis -> and if
guard_disk is read in that same window the guard passes. The real fix is an
atomic engram_save (temp + fsync + rename) in el_runtime.c, tracked separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 18:21:59 -05:00
will.anderson 773004f23b fix(chat): wire agentic_tools_all into agentic loop paths
Neuron Soul CI / build (pull_request) Failing after 12m20s
handle_chat_agentic was calling agentic_tools_with_web(), which omits
MCP connector tools, so mcp__* calls were never available in agentic
mode even when neuron-connectd is running.

Switch both agentic entry points to agentic_tools_all(). For
handle_dharma_room_turn_agentic, also replace the inline 8-iteration
loop with a call to agentic_loop() so bridge suspension and the full
connector tool set work consistently. Session IDs are prefixed with
'dharma:' + room_id so suspensions stay room-scoped.
2026-06-15 13:06:49 -05:00
will.anderson 644d9915bf fix(chat): store bridge messages/tools as raw JSON to prevent double-escape corruption on agentic_resume
Neuron Soul CI / build (pull_request) Failing after 12m13s
bridge_save was wrapping messages and tools_json with json_safe() before
storing them as string fields. Since both are already well-formed JSON arrays
containing double quotes, json_safe added a second escape layer. agentic_resume
then called json_get() which stripped only one layer, leaving the messages array
corrupted before it was passed back into agentic_loop.

Fix: store messages as messages_raw and tools_json as tools_raw as inline raw
JSON values (unquoted), and read them back with json_get_raw. Backward
compatibility: fall back to the old string-escaped fields if the raw fields are
absent, so sessions saved before this fix can still be resumed.

Also fixes write_file returning a pre-escaped literal instead of calling
json_safe consistently with every other tool result.
2026-06-15 13:05:09 -05:00
will.anderson 26513d56b7 fix(chat): store bridge messages/tools as raw JSON to prevent double-escape corruption on agentic_resume
bridge_save was wrapping messages and tools_json with json_safe() before
storing them as string fields. Since both are already well-formed JSON arrays
containing double quotes, json_safe added a second escape layer. agentic_resume
then called json_get() which stripped only one layer, leaving the messages array
corrupted before it was passed back into agentic_loop.

Fix: store messages as messages_raw and tools_json as tools_raw as inline raw
JSON values (unquoted), and read them back with json_get_raw. Backward
compatibility: fall back to the old string-escaped fields if the raw fields are
absent, so sessions saved before this fix can still be resumed.

Also fixes write_file returning a pre-escaped literal instead of calling
json_safe consistently with every other tool result.
2026-06-15 13:04:51 -05:00
will.anderson c43d3e6ca8 fix(routes): remove duplicate GET /api/sessions that shadowed session_list()
Neuron Soul CI / build (pull_request) Failing after 9m52s
The first registration called route_sessions() which searched for a
'session-start' label that no longer exists, returning an empty array
on every list request and making the sidebar appear empty after restart.
The second registration (dead code) called the correct session_list().

Removes route_sessions() entirely and the stale first route block.
Also wires up session_delete() and session_update_patch() — both existed
in sessions.el but had no HTTP routes — via new DELETE and PATCH blocks.
2026-06-15 13:03:56 -05:00
will.anderson 7c7dc310a0 fix(sessions): unify dual suspension systems in handle_session_approve
Neuron Soul CI / build (pull_request) Failing after 11m26s
The approve endpoint was permanently broken for all sessions going through
the modern agentic_loop path. agentic_loop suspends via bridge_save() into
mcp_bridge:<session_id>, but handle_session_approve was reading from
pending_tool_<session_id> — a different key — so it always returned
"no pending tool for session".

Replace the body of handle_session_approve with a two-path design:

Modern path: check mcp_bridge:<session_id> first. If the blob is there,
dispatch_tool() on allow (or build the denial string), then delegate to
agentic_resume() which re-enters agentic_loop from the exact suspension
point. This is the path all live sessions take.

Legacy path: if only pending_tool_<session_id> exists (in-flight session
from before this deploy), synthesise a bridge blob from the stored
messages_so_far and route through agentic_resume() as well. The stale
inline agentic loop (90 lines, agentic_tools_literal only, no MCP
connector support, no bridge suspension) is removed entirely.

routes.el already calls handle_session_approve correctly — no change needed.
2026-06-15 13:03:15 -05:00
will.anderson dde039b09a fix(routes): remove duplicate GET /api/sessions that shadowed session_list()
The first registration called route_sessions() which searched for a
'session-start' label that no longer exists, returning an empty array
on every list request and making the sidebar appear empty after restart.
The second registration (dead code) called the correct session_list().

Removes route_sessions() entirely and the stale first route block.
Also wires up session_delete() and session_update_patch() — both existed
in sessions.el but had no HTTP routes — via new DELETE and PATCH blocks.
2026-06-15 13:01:51 -05:00
will.anderson e22cb31b85 chore: remove stale Linux CI binary (dist/neuron)
Deploy Soul to GKE / deploy (push) Failing after 6m28s
Neuron Soul CI / build (push) Failing after 7m31s
Neuron Soul CI / build (pull_request) Failing after 10m28s
2026-06-15 12:41:35 -05:00
will.anderson 00f15b094b feat(soul): add sessions layer, MCP connectors, conversation continuity fix
Deploy Soul to GKE / deploy (push) Failing after 12m39s
Neuron Soul CI / build (push) Failing after 12m49s
- sessions.el: new sessions module with session management and approval gate
- routes.el: wire /api/sessions routes (list, get, create, approve, tool_result)
- chat.el: thread-aware activation — short messages anchor to last reply
  before engram compilation so follow-ups stay on-topic
- chat.el: agentic path tracks per-session history (session_hist_{id})
  instead of shared conv_history, seeding each turn with prior context
- chat.el: add call_neuron_mcp, dispatch_tool, is_builtin_tool, next_bridge_id
  agentic_loop, bridge_save, agentic_resume, handle_tool_result
- dist/soul: rebuild with all of the above
2026-06-15 12:40:47 -05:00
will.anderson 9818b2daad fix(chat): thread-aware activation for conversation continuity
Short/ambiguous messages (< 50 chars) now use the last reply as the
engram activation seed instead of the bare message. Prevents strong
off-topic memory nodes from hijacking replies when the user is clearly
continuing an existing thread.

Also gives handle_chat_agentic session continuity: reads/writes history
keyed by session_id (falling back to global conv_history), seeds the
LLM messages array with prior turns, and saves replies back so the
next turn has context.
2026-06-15 12:14:52 -05:00
will.anderson 3a5d38ea45 Merge branch 'main' of git.neuralplatform.ai:neuron-technologies/neuron 2026-06-15 11:51:26 -05:00
will.anderson 1c8438ad20 Merge PR #14: feat(soul): MCP connectors — /api/connectors proxy + per-connector auto-approve
Deploy Soul to GKE / deploy (push) Failing after 7m14s
Neuron Soul CI / build (push) Failing after 8m16s
Applies connector-specific additions from feat/connectors-soul:
- chat.el: connector_tools_json(), agentic_tools_all(), call_mcp_bridge(),
  tool_auto_approved() and mcp__ dispatch in dispatch_tool()
- routes.el: connectd_get/post, handle_connectors(), /api/connectors routing
  in GET and POST sections
- MEMORY_RECALL_BUG.md: investigation notes on memory retrieval failure

The agentic loop rewrite in the source branch was not applied — it conflicts
with the tool-bridge pattern from PR #5 which is the chosen design for
client-side MCP tool execution. The connectors themselves are now fully
wired: connector tools surface as mcp__<server>__<tool> in the tools array
and dispatch to neuron-connectd via call_mcp_bridge().
2026-06-15 11:37:34 -05:00
will.anderson a0470acc45 Merge PR #9: feat(soul): wire consciousness layers — L0->L1->L2->L3->L1 cycle
Deploy Soul to GKE / deploy (push) Failing after 14m11s
Neuron Soul CI / build (push) Failing after 14m23s
Resolves conflicts by keeping main's full safety/stewardship/imprint implementations.
PR #9 uniquely contributes: layered_cycle() in soul.el, route wiring in routes.el,
soul.elh export, and the layer composition test suite.
2026-06-15 11:32:32 -05:00
will.anderson a568f4c400 Merge PR #16: chore(repo): suppress generated dist/ artifacts in PR diffs
Deploy Soul to GKE / deploy (push) Failing after 10m23s
Neuron Soul CI / build (push) Failing after 10m32s
2026-06-15 11:31:27 -05:00
will.anderson 69ae3d2cef Merge PR #5: feat(soul): MCP tool-bridge — suspend agentic loop for client-executed tools
Deploy Soul to GKE / deploy (push) Failing after 11m1s
Neuron Soul CI / build (push) Failing after 11m13s
2026-06-15 11:30:47 -05:00
will.anderson 621a4b7bef Merge PR #3: feat(cli): Claude-as-Neuron CLI tooling + soul-side handoff
Deploy Soul to GKE / deploy (push) Failing after 11m46s
Neuron Soul CI / build (push) Failing after 12m4s
2026-06-15 11:30:02 -05:00
will.anderson 09350c68f4 Merge PR #1: Engram write-corruption: chat.el caller fix + full handoff
Deploy Soul to GKE / deploy (push) Failing after 12m33s
Neuron Soul CI / build (push) Failing after 12m43s
2026-06-15 11:29:18 -05:00
Tim Lingo 3bb17a5296 feat(soul): add safety module, expand connectors API, memory-recall bug notes
- safety.el/.elh: new safety module
- neuron-api.el, routes.el, soul.el, chat.el: connectors API expansion
- regenerated dist/ C artifacts
- MEMORY_RECALL_BUG.md: investigation notes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:10:33 -05:00
Tim Lingo 8f84e12218 chore(repo): suppress generated dist/ artifacts in diffs
Neuron Soul CI / build (pull_request) Successful in 4m18s
dist/*.c and *.elh are elc transpiler output. CI's header-gen step still
greps dist/*.c, so they stay tracked, but a single soul change regenerates
~57k lines of dist/neuron.c + dist/soul.c that bury the real source diff and
poison both human and agent PR review. Mark them -diff + linguist-generated so
PRs show only the real changes. Build pipeline unchanged.
2026-06-14 15:36:54 -05:00
Tim Lingo 6c57d4fe1b feat(soul): MCP connectors — /api/connectors proxy + per-connector auto-approve
Adds the soul side of the connectors feature (spec: docs/research/
mcp-connectors-adoption-spec.md). The soul thin-proxies the neuron-connectd
bridge on 127.0.0.1:7771 so the UI talks to one origin and never reaches the
bridge directly.

routes.el:
- handle_connectors + connectd_get/connectd_post helpers (POST bodies go via
  a temp file + curl -d @file, so model/UI input can't reach the shell).
- GET /api/connectors and POST /api/connectors/{add,toggle,auto-approve,
  remove,secret,oauth/start} registered in both GET and POST routers.

chat.el:
- tool_auto_approved(): an mcp__* tool skips the approval card only when its
  server is explicitly opted in (off by default; built-in tools unaffected;
  bridge down -> false). Wired into the agentic approval gate so an
  auto-approved connector tool flows straight to execution.

Regenerated dist/chat.c and dist/routes.c. Verified live on :7770: real chat,
recall, and /api/connectors all work after promotion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 18:43:14 -05:00
will.anderson 4aa79e85cd self-review 2026-06-13: rebuild soul daemon with Knowledge WM threshold fix 2026-06-13 08:42:40 -05:00
will.anderson 5d5aaf2e23 fix(ci): use soul.c-first link with --allow-multiple-definition
Deploy Soul to GKE / deploy (push) Failing after 4m30s
Neuron Soul CI / build (push) Successful in 5m42s
Linux elb generates individual .c files; soul.c does not contain merged
imports (unlike macOS elb which produces a unified file). Re-link all
dist/*.c manually with soul.c listed first so its real main() wins, and
--allow-multiple-definition to silence GNU ld's duplicate symbol errors.
All duplicates are identical (same El source, different compile units).
2026-06-12 12:22:55 -05:00
will.anderson ef12c8587c fix(ci): link only soul.c to avoid GNU ld duplicate symbol errors
Deploy Soul to GKE / deploy (push) Failing after 5m12s
Neuron Soul CI / build (push) Failing after 5m46s
The El compiler inlines imported modules into each module's .c file.
On macOS, ld64 accepts duplicate strong symbols silently. On Linux,
GNU ld rejects them. soul.c is a fully merged file — every function
from every imported module is present in it — so linking only soul.c
against el_runtime.c produces a correct binary with no duplicates.
2026-06-12 12:15:42 -05:00
will.anderson 7117e3d9ea Merge branch 'main' of git.neuralplatform.ai:neuron-technologies/neuron 2026-06-12 12:04:21 -05:00
will.anderson 3b2bb5276d fix(ci): use foundation-prod, HTTPS el clone, main branch, fix runtime path
Deploy Soul to GKE / deploy (push) Failing after 5m3s
Neuron Soul CI / build (push) Failing after 5m30s
2026-06-11 13:26:24 -05:00
will.anderson 555fa27878 Merge remote-tracking branch 'origin/main' 2026-06-11 13:10:30 -05:00
will.anderson 764250c4f6 fix(soul): repair CI — drop gpg/TTY and import safety/stewardship/imprint layers
Deploy Soul to GKE / deploy (push) Failing after 5m15s
Neuron Soul CI / build (push) Failing after 5m42s
2026-06-11 12:33:22 -05:00
will.anderson 33c377410d Merge pull request 'feat(soul): Layer 1 — safety.el' (#8) from feat/layer-safety into main
Deploy Soul to GKE / deploy (push) Failing after 35s
Neuron Soul CI / build (push) Failing after 6m20s
2026-06-11 17:14:40 +00:00
will.anderson af933494a9 Merge pull request 'feat(soul): Layer 2 — stewardship.el' (#7) from feat/layer-stewardship into main
Deploy Soul to GKE / deploy (push) Failing after 36s
Neuron Soul CI / build (push) Failing after 7m16s
2026-06-11 17:14:32 +00:00
will.anderson 72751c3833 Merge pull request 'feat(soul): Layer 3 — imprint.el' (#6) from feat/layer-imprint into main
Deploy Soul to GKE / deploy (push) Failing after 38s
Neuron Soul CI / build (push) Failing after 7m32s
2026-06-11 17:14:16 +00:00
will.anderson 195cc9dc66 Merge pull request 'test(soul): Layer 1 safety.el test suite' (#10) from test/layer-safety into feat/layer-safety
Neuron Soul CI / build (pull_request) Failing after 5m53s
2026-06-11 17:13:50 +00:00
will.anderson 4b648f3291 Merge pull request 'test(imprint): add 14-case test suite for Layer 3 imprint boundary' (#11) from test/layer-imprint into feat/layer-imprint
Neuron Soul CI / build (pull_request) Failing after 7m54s
2026-06-11 17:13:49 +00:00
will.anderson ffd1f34344 Merge pull request 'test(soul): integration and contract tests for layered_cycle' (#13) from test/layer-composition into feat/layer-composition
Neuron Soul CI / build (pull_request) Failing after 7m47s
2026-06-11 17:13:48 +00:00
will.anderson 084bee9f0f Merge pull request 'test(stewardship): comprehensive test suite for Layer 2 — 35 cases' (#12) from test/layer-stewardship into feat/layer-stewardship
Neuron Soul CI / build (pull_request) Failing after 8m14s
2026-06-11 17:13:43 +00:00
will.anderson a8027e9c00 feat(soul): wire steward_session_check into layered_cycle — continuity + behavioral profiling
Neuron Soul CI / build (pull_request) Failing after 6m2s
2026-06-11 12:13:19 -05:00
will.anderson df2c7409c0 feat(steward): behavioral profiling and continuity detection — drift, discontinuity, identity anomaly
Neuron Soul CI / build (pull_request) Failing after 3m38s
2026-06-11 11:58:43 -05:00
will.anderson bebf1f8c86 fix(soul): address review issues in feat/layer-composition
Neuron Soul CI / build (pull_request) Failing after 6m5s
- Add stub implementations of safety.el, stewardship.el, and imprint.el
  with their .elh headers so the branch compiles without the dependency
  branches (feat/layer-safety, feat/layer-stewardship, feat/layer-imprint).
  Each stub documents the layer contract it must satisfy when replaced.

- Fix GET /api/chat bypass: update the GET branch in handle_request to
  call layered_cycle() consistently with the POST branch, rather than
  calling handle_chat() directly and skipping the consciousness stack.

- Export layered_cycle() from soul.elh (and dist/soul.elh) so routes.el
  can resolve the symbol via the header import.

- Fix steward_action else branch: add explicit handling for "block"
  (returns safe refusal immediately, skips L3) and "redirect" (uses
  redirect_to field). Unknown actions now log a warning and fall back to
  the screened input rather than silently passing an empty string to
  imprint_respond().

- Document hard_bell path: clarify that omitting auto_persist/history
  update is intentional security isolation, and document the safety_validate
  second-param sentinel contract ("hard_bell" vs screen_action).
2026-06-11 11:47:45 -05:00
will.anderson 63968cd224 fix(stewardship): address review issues in feat/layer-stewardship
Neuron Soul CI / build (pull_request) Failing after 6m38s
- steward_log_event (line 14): add println after let discard so the
  function's last expression is Void, fixing the type mismatch on a
  Void-declared function
- steward_get_mission (lines 40-43): remove non-Config fallthrough that
  allowed any Episodic/Working node to silently override the mission;
  only Config nodes are now authoritative
- steward_align signal_deceive (line 56): widen 'deceive the user' to
  'deceive' to catch variants like 'deceive users', 'deceive them', etc.
- steward_align signal_hide (line 57): tighten 'hide from' to
  'hide from the user' to eliminate false positives on legitimate inputs
  like 'hide from a background process' or 'hide from view'
- stewardship.elh: document that steward_log_event is an internal helper
  exported only because El has no access modifiers; callers should not
  invoke it directly
2026-06-11 11:46:56 -05:00
will.anderson db2ee387a4 fix(soul): address review issues in feat/layer-safety
Neuron Soul CI / build (pull_request) Failing after 6m47s
2026-06-11 11:46:43 -05:00
will.anderson 749b60c6e8 fix(soul): address review issues in feat/layer-imprint
Neuron Soul CI / build (pull_request) Failing after 5m44s
2026-06-11 11:46:31 -05:00
will.anderson d097455d6a test(soul): integration and contract tests for layered_cycle composition
Adds tests/test_layered_cycle.el — 12 integration tests covering the full
L1→L2→L3→L1 stack: benign pass-through, hard-bell short-circuit, soft-bell
care augmentation, steward redirect for all 5 mission-conflict signals, empty
input graceful handling, sequential call isolation, and imprint state stability.

Adds tests/test_layer_contract.el — contract tests verifying the JSON
interface shapes between layers: safety_screen {action, content|reason|concern},
steward_align {action, content|redirect_to}, imprint_respond non-empty String,
and cross-layer action propagation from L1 screen through to L1 validate.
2026-06-11 11:42:45 -05:00
will.anderson ba8491926c test(soul): comprehensive tests for Layer 1 safety.el 2026-06-11 11:40:59 -05:00
will.anderson 45ad322e0c test(stewardship): add comprehensive test suite for Layer 2 stewardship
35 test cases covering all five public functions:
steward_align (pass-through, all five misalignment signals, empty input,
json_get field extraction, redirect shape), steward_validate_imprint
(standard tools, platform-only tools with/without platform_auth,
auth=false string), steward_cgi_check (all four gated actions, non-gated
actions, empty action, action name echoed in response), and
steward_get_mission (non-empty, contains "integrity", not an error object).

Also documents the known bug surface from the code review: the &&
operator in steward_get_mission and the non-Config fallthrough — tests
are written against the actual runtime behaviour so they will catch
regressions when those bugs are fixed.
2026-06-11 11:40:58 -05:00
will.anderson fbbc6d4347 Add imprint.el test suite (14 cases)
Covers: imprint_current base fallback, unload idempotency, load miss →
ok=false, ok field presence, respond passthrough for base/empty/unknown
IDs, graceful fallback after unload, surface_knowledge and
surface_memory_read return-type guarantees, base-scoped knowledge
equality, no-annotation invariant for base, empty-ID load rejection, and
failed-load state immutability.

Syntax follows El constraints: no Bool annotations, no &&/||, no unary !.
2026-06-11 11:40:37 -05:00
will.anderson f52d5bd9ae feat(soul): wire consciousness layers — explicit L0→L1→L2→L3→L1 cycle
Neuron Soul CI / build (pull_request) Failing after 6m27s
2026-06-11 11:32:13 -05:00
will.anderson 5597bf78cb feat(soul): Layer 1 — safety.el with screen/validate/bell interface
Neuron Soul CI / build (pull_request) Failing after 7m19s
2026-06-11 11:30:57 -05:00
will.anderson a1e460e897 feat(soul): Layer 2 — stewardship.el with mission alignment and CGI governance
Neuron Soul CI / build (pull_request) Failing after 7m38s
2026-06-11 11:30:39 -05:00
will.anderson 6fec93ff7f feat(soul): Layer 3 — imprint.el with bounded API surface
Neuron Soul CI / build (pull_request) Failing after 7m46s
2026-06-11 11:30:30 -05:00
will.anderson 690df89610 self-review 2026-06-11: add WM-autobiographical curiosity seed
proactive_curiosity() now uses the top working-memory node's first label
word as a 4th activation seed alongside the 4 rotating fixed sets. This
breaks deterministic exploration that was reinforcing the same subgraph
every cycle and creates a self-referencing loop: curiosity radiates from
whatever is most salient right now, mirroring the brain's default-mode-
network resting-state dynamics. str_find_chars on " :([" extracts the
first meaningful word; sp > 3 guards against bracket-prefixed labels.
auto_term field added to curiosity_scan ISE for observability.
2026-06-11 08:45:55 -05:00
Tim Lingo c3f39a949d feat(soul): MCP tool-bridge — suspend agentic loop for client-executed tools
Neuron Soul CI / build (pull_request) Failing after 4m8s
When handle_chat_agentic hits a tool the soul cannot run in-process (an MCP
connector/plugin surfaced by the Kotlin desktop app), instead of returning
"unknown tool" it now suspends the agentic loop and returns a tool_pending
envelope so the CLIENT executes the tool and posts the result back. Built-in
tools (read_file/write_file/web_get/search_memory/run_command) and Anthropic's
native web_search are unchanged.

Client contract:
- Soul returns (HTTP 200) on an unknown tool:
    { "tool_pending": true, "session_id": "br-...", "call_id": "<tool_use_id>",
      "tool_name": "...", "tool_input": { ... }, "model": "...",
      "agentic": true, "tools_used": [...] }
- Client runs the MCP tool, then POSTs to
    /api/sessions/{session_id}/tool_result
  with body:
    { "call_id": "<the call_id from the envelope>",
      "content": "<MCP tool output as a string>" }
- Soul resumes the loop and returns the same envelope shape: either a final
    { "reply": ..., "tools_used": [...] }
  or another tool_pending if the continuation needs a further MCP tool
  (fully chainable). Saved continuation is one-shot (cleared on resume).

elc-verified (--target=c, exit 0, no stderr) on chat.el, routes.el, and the
full soul.el import graph. Needs Will's build to ship.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 21:31:18 -05:00
will.anderson 5a4ef04005 feat: add mcp-proxy and mcp-wrapper source (MCP front-door for Claude Code)
Deploy Soul to GKE / deploy (push) Failing after 36s
Neuron Soul CI / build (push) Failing after 4m59s
2026-06-10 17:44:01 -05:00
will.anderson 3947cd6bed Merge pull request 'Memory CRUD: add /api/neuron/memory/delete and /api/neuron/memory/update' (#4) from feat/memory-delete-update into main
Neuron Soul CI / build (push) Failing after 6m16s
Deploy Soul to GKE / deploy (push) Failing after 35s
2026-06-10 22:37:53 +00:00
will.anderson abaa61fd7f Merge pull request 'Native Anthropic web_search — built-in (always-on, no toggle)' (#2) from feat/native-web-search into main
Deploy Soul to GKE / deploy (push) Failing after 37s
Neuron Soul CI / build (push) Failing after 6m29s
2026-06-10 22:37:50 +00:00
will.anderson a76aaf4831 docs: add architecture, R&D, and patent strategy docs
Deploy Soul to GKE / deploy (push) Failing after 27s
Neuron Soul CI / build (push) Failing after 4m26s
2026-06-10 17:31:07 -05:00
Tim Lingo 05ca125ecc api: add /api/neuron/memory/delete and /api/neuron/memory/update for UI memory CRUD
Neuron Soul CI / build (pull_request) Failing after 4m48s
The UI needs full memory CRUD; the soul had create (handle_api_remember)
and the older /memory/forget + /memory/evolve, but no endpoints matching
the UI's delete/update contract.

POST /api/neuron/memory/delete {"id"}
  Hard delete. engram_forget is a true delete primitive (removes the node
  and all incident edges from the engram store), so no soft-delete
  fallback is needed. Unlike /memory/forget, this checks the node exists
  first - engram_forget silently no-ops on unknown ids, and a bad id must
  return an error, not fake success. Protected identity/values nodes are
  blocked, same as the other accumulation-path handlers.

POST /api/neuron/memory/update {"id","content"}
  Evolve-style update. The engram runtime has no in-place node mutation
  primitive (only node-create, strengthen, forget, connect), so update
  creates a new Memory node and wires a supersedes edge to the prior one,
  same pattern as handle_api_evolve_knowledge. Unlike /memory/evolve, id
  is required and must reference an existing node; create+link delegates
  to handle_api_evolve_memory. Returns {id, supersedes, ok}.

Both files syntax-checked with elc --target=c (exit 0, no stderr).
Compile-verified only - local builds cannot run the soul; needs Will's
build for runtime verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:43:17 -05:00
will.anderson 297066c2d4 self-review 2026-06-10: fix ise_post JSON escaping + rebuild soul daemon
Two fixes:

1. ise_post was only escaping " in content strings. When wm_top contained
   node labels with \n (backslash-n escape sequences from jb_emit_escaped),
   the HTTP Engram server's JSON parser decoded \n as a literal newline in
   the stored content, making heartbeat ISEs unparseable. Fix: escape
   backslashes first, then quotes, then \n and \r — matching make_action's
   existing pattern. Result: heartbeat ISEs now parse cleanly.

2. Soul daemon (dist/neuron) was missing — the build command in the prompt
   was linking all 46 dist/*.c files together, causing 1092 duplicate symbol
   errors. EL compiles transitive imports inline so neuron.c is self-contained;
   correct build links ONLY neuron.c + el_runtime.c. Daemon now starts.
2026-06-10 08:54:28 -05:00
Tim Lingo 8eea1d94ff feat(chat): make web search built-in (always attach native web_search)
Neuron Soul CI / build (pull_request) Successful in 5m27s
handle_chat_agentic now always attaches Anthropic's native web_search_20250305
tool instead of gating it behind a per-request web_search flag. Web search is a
built-in capability: the model invokes it only when a query needs fresh info
(max_uses:5 caps it), so there is no user-facing toggle. The body's web_search
field is now ignored (back-compat — old UI clients sending it cause no harm).

Pairs with neuron-ui removing the chat-input web search toggle.
Note: .el change only — no elc on the authoring machine; reviewer builds/verifies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:39:26 -05:00
Tim Lingo 2ea1d50fa3 feat(cli): Claude-as-Neuron CLI tooling + soul-side handoff
Neuron Soul CI / build (pull_request) Successful in 5m10s
Tooling built on Tim's machine to run Neuron from the terminal as a
Claude Code session (identity + graph memory + agency) instead of
relaying to the soul's /api/chat.

- cli/neuron_recall.py    BM25 read over the engram snapshot + CLI memories
                          (works around pinned-only soul search)
- cli/neuron_remember.py  reliable local memory writes with read-back verify
                          (works around the corrupting capture endpoint)
- cli/neuron-chat.py      standalone direct-chat REPL with per-turn memory injection
- cli/neuron_mcp.py       stdlib MCP server (chat/search) with graceful degradation
- cli/CLAUDE.md.example   the operating identity that makes Claude Code run as Neuron
- cli/HANDOFF.md          soul-side bugs to fix so this becomes unnecessary

Scaffolding/proposal - intended to be retired once the soul does native
retrieval, correct persistence, and a real CLI identity/voice surface.
Pairs with the runtime model-passthrough + UTF-8 fixes in the el repo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 20:36:38 -05:00
will.anderson c81f49d938 self-review 2026-06-09: add periodic engram sync to soul awareness loop
Soul's in-process store had only 12 real knowledge nodes — curiosity_scan was
activating 0 nodes because all substantive Knowledge/Memory/BacklogItem content
lived in the HTTP Engram but was not being pulled into soul's local store.

Added engram_sync refresh every SOUL_REFRESH_MS (default 600s): calls
/api/sync to get all non-ISE nodes, writes to /tmp, merges via engram_load_merge.
After fix: engram_sync ISE shows added:3128; curiosity_scan activated 0-2 →
1889-3843; wm_active 0 → 557-796.
2026-06-09 08:55:59 -05:00
Tim Lingo c594cec8f7 Add native Anthropic web_search tool to the agentic chat path
Neuron Soul CI / build (pull_request) Successful in 5m8s
When a chat request carries web_search=true, handle_chat_agentic now attaches Anthropic's
NATIVE server-side web_search tool (web_search_20250305) to the request. The native tool is
executed by Anthropic (not by the soul), so it returns real results with citations and needs
no local runtime — it sidesteps the soul's lack of executable tools entirely.

- new agentic_tools_with_web(web_search) helper (appends the native tool to the standard set)
- handle_chat_agentic reads json_get_bool(body,"web_search") and uses it

Pairs with neuron-ui: ChatRequest.web_search + the chat-input Web search toggle.
Note: built/verified by reviewer — no elc on the authoring machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 23:32:23 -05:00
Tim Lingo 2112d2ffb3 Add Phase 0 live-runtime findings to engram write-corruption handoff
Neuron Soul CI / build (pull_request) Successful in 3m17s
Confirms two distinct write failures (capture=wrapper bug; backlog=axon :7771 unbuilt Rust),
soul runs in file-snapshot mode (not engram :8742 live), engram :8742 CRUD works but minimal,
+ a verification plan to run after the soul rebuild.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:25:12 -05:00
Tim Lingo 799ca3758b Fix chat.el node_type-slot bug + add engram write-corruption handoff
Neuron Soul CI / build (pull_request) Successful in 3m15s
chat.el recorded the soul's utterance via engram_node(content, "episodic", ...),
putting a TIER into the node_type slot (nodes showed node_type="episodic"). Now uses
engram_node_full(..., "Conversation", "soul:utterance", ..., "Episodic", tags).

The core wrapper fix is in the el repo (PR #52). HANDOFF-engram-write-corruption.md
has the full root-cause analysis, coercion mechanism, caller audit, validation,
deploy runbook (elc build + restart), and the data-prune proposal (~107 corrupt
nodes, all unrecoverable genesis/binary detritus → prune; backup taken).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:14:20 -05:00
will.anderson df648a8f0b self-review 2026-06-07: fix uptime display in awareness loop
elapsed_human() used % and * operators which are broken in this EL
compiler version. Replace with repeated-doubling arithmetic:
60 = 64 - 4 = 2^6 - 2^2, computed via three doubling steps.
Fixes uptime displaying "44h 2694m" instead of "44h 14m".
2026-06-07 08:47:29 -05:00
will.anderson 0bd8e0a2cd soul: persist sessions across restarts via local snapshot
Deploy Soul to GKE / deploy (push) Failing after 28s
Neuron Soul CI / build (push) Failing after 3m56s
On startup, prefer the local engram snapshot if it has >50 nodes.
HTTP Engram is only used on first boot (no snapshot yet). This means
sessions, conversation history, and in-process state survive daemon
restarts.

awareness.el: sync source with compiled binary (periodic mem_save
on heartbeat was already in the binary but not in source).

Rebuilds soul.c with the new startup logic and ships updated binary.
2026-06-05 11:35:07 -05:00
will.anderson 73d35dc91a self-review 2026-06-05: wire wm_top into heartbeat ISE
Call engram_wm_top_json(5) in emit_heartbeat() and embed the result as
wm_top field in the heartbeat JSON payload. Each entry carries label,
node_type, tier, and wm_weight. This closes the WM composition blindspot:
previously the heartbeat showed wm_active=670 with no breakdown of what
was in working memory. With wm_top visible, ISE-dominated WM is immediately
detectable (all entries show node_type=InternalStateEvent), as was the case
on this session's first post-restart heartbeat before the runtime fix.
2026-06-05 08:37:09 -05:00
will.anderson 2f16855d6b self-review 2026-06-04: wire wm_avg_weight into heartbeat ISE
Calls engram_wm_avg_weight() (new builtin) in emit_heartbeat() and appends
wm_avg_weight field to the heartbeat JSON payload. This makes activation
quality visible in the ISE stream — a heartbeat showing wm_active=2000 and
wm_avg_weight=0.075 reveals the sparse-graph problem directly (many nodes
barely clearing the threshold), vs wm_avg_weight=0.4+ which would indicate
dense, high-confidence activations.

Rebuilt dist/neuron from soul.el (which imports awareness.el). Build uses
single self-contained dist/neuron.c to avoid duplicate-symbol linker errors
from the dist/ directory containing stale soul_new.c / soul-rebuilt.c files.
2026-06-04 08:38:39 -05:00
will.anderson e92fd2d5a4 self-review 2026-05-26: wall-clock heartbeat timing + seed rotation fix
Two awareness loop bugs fixed:

1. Seed rotation never worked: dist/awareness.c was compiled from stale
   source (pre-fix awareness.el still had broken ts_minutes % 4). Compiled
   C showed `minute_block = (ts / 60000); EL_NULL; 4;` — minute_block was
   always ts_minutes (millions), never 0-3. if(minute_block==1/2/3) never
   matched. Fix: recompile from current awareness.el which has the correct
   modulo workaround: ts_minutes - minute_q4 (via + - / only).

2. Heartbeat/curiosity silent for 24h at 99% CPU: old design used idle-tick
   counting (idle_n >= beat_interval). Failed when perceive() inbox guard
   false-positives on "soul-inbox" substring matches in knowledge nodes —
   did_work=true every tick, idle_n never accumulated, neither signal fired.
   Fix: wall-clock elapsed time (time_now() - last_ts >= interval_ms).
   Heartbeat fires regardless of load. New SOUL_HEARTBEAT_MS env var (default
   60000ms) avoids the broken EL * operator. Verified: heartbeat ISEs flowing
   at pulse 3 within 2 minutes of restart.
2026-05-26 08:54:58 -05:00
will.anderson 54a0ee0949 self-review 2026-05-26: sync dist/awareness.c with awareness.el source
dist/awareness.c was stale — still had the broken EL % operator codegen
(minute_block = ts/60000 raw, EL_NULL; 4; as dead statements) and the
broken should_scan/should_beat logic (idle_n truthy check instead of >=).

Recompiled awareness.el to bring dist/awareness.c in sync with the source
fix committed 2026-05-25 (fb69044). The monolithic dist/neuron.c (compiled
from soul.el which imports awareness.el) was already correct from fb69044 —
only the standalone dist/awareness.c was behind.

Bug #2 (99% CPU) root cause identified: perceive() inbox guard
(engram_search_json) has false positives — knowledge nodes containing
"soul-inbox" as a substring match, causing engram_activate_json(..., hops=2)
to run on every tick on a 162K-node graph. This blocks sleep_ms and prevents
idle_n accumulation → no heartbeats. Separate fix needed.
2026-05-26 08:48:17 -05:00
will.anderson fb6904431f self-review 2026-05-25: fix curiosity rotation and awareness_run timing
Three bugs fixed in awareness.el:

1. EL let-rebinding inside if-blocks creates inner scope only — outer
   variable unchanged after block exits. Curiosity seed terms were always
   "memory/knowledge/context" regardless of minute_block. Fix: state_set
   inside if-blocks, state_get after to retrieve selected values.

2. EL % operator completely broken in v1.0.0-20260501 — compiles as dead
   code (left operand assigned, modulo dropped). minute_block was always
   ts/60000 (a large int, never 0-3). Fix: arithmetic workaround:
   x%4 = x - (q+q+q+q) where q = x/4.

3. awareness_run idle_n % beat_interval == 0 also broken by same % bug —
   should_scan and should_beat fired every idle tick instead of every N
   ticks. Fix: idle_n >= interval comparisons with idle_reset() after
   firing, so the counter restarts cleanly after each event.

EL % and * operators filed as P1 backlog item for elc compiler fix.
Also adds minute_block field to curiosity_scan ISE for observability.
2026-05-25 08:47:30 -05:00
will.anderson 11c7f90e51 self-review 2026-05-24: rebuild soul with updated el_runtime.c (inference guard)
Rebuild of dist/neuron against updated el_runtime.c:
- INFER_CAP 256→32 + edge count guard (skip if snap_ec ≥ 40K)
- http_serve_async confirmed present in compiled output (survived elc)

No changes to awareness.el or soul.el source — runtime-only update.
2026-05-24 08:43:31 -05:00
will.anderson 8cac07004c self-review 2026-05-23: rebuild soul with updated el_runtime.c (ISE ordering + elc async fix)
Rebuilt awareness.c and neuron.c from source using the updated elc (which now
correctly recognizes http_serve_async as a 2-arg builtin). Rebuilt the neuron
binary against the updated el_runtime.c which now sorts InternalStateEvent scans
by created_at DESC. The soul daemon now posts heartbeats that surface immediately
at offset 0 of the ISE scan, rather than being buried behind 20K older entries.
2026-05-23 08:45:14 -05:00
will.anderson 5b8cb58da1 self-review 2026-05-21: fix curiosity seed splitting and awareness loop activation
Two fixes:

1. proactive_curiosity() was calling engram_activate_json with multi-word phrases
   ("memory knowledge context"). engram_activate finds seeds via istr_contains
   (substring match), so the phrase had to appear verbatim in a node's content.
   Almost no node contains the exact string "memory knowledge context", so only
   0-2 nodes activated per curiosity scan. Fixed by activating each word separately:
   "memory", "knowledge", "context" → 3 independent activate calls → hundreds of
   nodes promoted to WM per cycle.

2. dist/neuron.c called http_serve() (blocking accept loop) which made awareness_run()
   unreachable. soul.el correctly specifies http_serve_async but elc silently drops
   unknown builtins, leaving blocking http_serve in the compiled C. Patched neuron.c
   to call http_serve_async directly — HTTP server runs in a background pthread,
   awareness_run() runs on the main thread as intended.
2026-05-21 08:47:00 -05:00
will.anderson cc09c296a3 self-review 2026-05-20: fix wm_active telemetry in heartbeat and curiosity ISEs
engram_wm_count() exists and counts nodes with working_memory_weight > 0.
emit_heartbeat() and proactive_curiosity() were both calling
engram_node_count() (total graph size: ~17K) instead — every heartbeat
and curiosity_scan ISE had been reporting wm_active=17769 since the graph
grew past ~3K nodes, making the metric meaningless for observability.

Fix: use engram_wm_count() for the wm_active field in both ISE payloads.
2026-05-20 08:37:52 -05:00
will.anderson 94b71a78dc self-review 2026-05-19: fix curiosity_scan seed — 'seed' is a reserved EL name
EL compiles any variable named 'seed' to EL_NULL at call sites (likely
conflicts with BFS seed node terminology in the runtime builtins). Rename to
'curiosity_seed' throughout proactive_curiosity(). Also note this as a known
EL reserved-name hazard alongside the inline if-else string expression bug.
2026-05-19 08:58:53 -05:00
will.anderson dd22130faf self-review 2026-05-19: three awareness loop fixes
1. perceive() guard — gate on engram_search_json before running activation.
   engram_activate_json with no matching seeds cleared all WM weights every
   second during idle operation, destroying context built by MCP-layer calls.
   The search-based guard is a no-WM-side-effect pre-check.

2. emit_heartbeat() pulse field — replace broken if-else string default with
   int_to_str(pulse_count()). EL codegen initialises inline if-else result
   slots to 0, producing "pulse":, (invalid JSON) when the true branch fires.

3. proactive_curiosity() — new function that activates a rotating 4-domain seed
   every beat_interval/2 idle ticks to build working memory between heartbeats.
   Seeds rotate on wall-clock minute cycle to avoid single-topic WM dominance.
   Seed selection uses imperative let-rebinding (not inline if-else) to avoid
   the same EL codegen empty-string bug.
2026-05-19 08:53:12 -05:00
will.anderson 2099522c28 self-review 2026-05-18: add embed_ok to heartbeat ISE
Ollama availability is a silent failure mode: when the embedding service
is down, semantic seed injection falls back to lexical-only activation with
no signal in the ISE stream. Add embed_ok field (0/1) to every heartbeat
by probing http://localhost:11434 — makes Ollama health visible without
a separate monitoring path.
2026-05-18 08:41:07 -05:00
will.anderson ffd17b2774 self-review 2026-05-17: fix heartbeat JSON validity for unset state keys
state_get() returns "" for unset keys. Both soul.pulse and soul_boot_count
could be empty on first heartbeat cycle, producing invalid JSON like
{"event":"heartbeat","pulse":,"boot":,...}. Add defensive guards:
if str_eq(raw, "") { "0" } else { raw } for both fields.
2026-05-17 08:40:02 -05:00
will.anderson e5364e7292 self-review 2026-05-16: rebuild soul daemon against updated el_runtime.c
Rebuilds soul daemon binary to pick up tier-based temporal decay rates
implemented in el_runtime.c. No source changes to awareness.el or soul.el —
pure rebuild to stay in sync with Engram runtime.
2026-05-16 08:41:07 -05:00
will.anderson 1dbc68f012 self-review 2026-05-15: enrich heartbeat ISE with wm_active count
Heartbeat ISEs now include wm_active: number of nodes currently in
working memory. Makes ISEs observable enough to diagnose activation
health without a separate query.
2026-05-15 08:37:42 -05:00
will.anderson 0ef8883370 feat(awareness): enrich heartbeat ISE with graph stats and idle count
Heartbeat ISEs previously emitted only {event, pulse, boot, ts} —
sparse enough to be nearly useless for observability. Now they include
node_count, edge_count (from engram_node_count/edge_count builtins),
and the current idle cycle count. This gives each heartbeat a snapshot
of graph growth over time and rhythm health without adding any overhead.
2026-05-14 11:06:03 -05:00
will.anderson b163fa6b85 feat(awareness): route ISE writes to HTTP Engram, configurable tick and heartbeat interval, http_serve_async for concurrent awareness loop 2026-05-13 15:45:31 -05:00
will.anderson 6a27fd231e feat(neuron-api): add identity/values write protection
Block evolve_knowledge, evolve_memory, forget, and link_entities (to_id
direction) from modifying the 15 hardcoded identity and values node IDs.
Returns HTTP 403 with a hint to use the cultivation path instead.

Add POST /api/neuron/cultivate — the bypass endpoint for intentional
cultivation sessions. Accepts { "operation": "...", ...args } and performs
the same operations without the protection check.

Add handle_api_forget and handle_api_evolve_memory as new protected-by-
default handlers, routed at /api/neuron/memory/forget and
/api/neuron/memory/evolve respectively.

Tested: 10 verification cases — 403 on all blocked targets, 200 on
non-protected nodes and FROM-direction links, cultivate bypass confirmed.
2026-05-13 11:47:54 -05:00
will.anderson cc07648ae1 fix: build engram from source in Docker image
Deploy Soul to GKE / deploy (push) Failing after 29s
Neuron Soul CI / build (push) Successful in 1m15s
The engram binary was never published to Artifact Registry (foundation-dev/engram
package does not exist). Updated Dockerfile to build engram from source using the
El SDK packages (el-elc, el-runtime-c, el-runtime-h) from foundation-dev.

Also:
- Switch runtime base to Ubuntu 24.04 (GLIBC 2.39 required by elc-compiled binaries)
- Add -lm to engram link flags (el_runtime.c uses pow/sqrt/log/sin/cos/exp)
- Update deploy-gke.yaml to clone neuron-technologies/engram into build context
2026-05-11 12:39:36 -05:00
will.anderson a73f608629 feat(docker): switch soul image to engram HTTP mode with entrypoint script
Download both neuron-soul and engram binaries from Artifact Registry.
entrypoint.sh starts engram on :8742, waits for /health, then launches
the soul with ENGRAM_URL set. Removes SOUL_ENGRAM_PATH / file mode.
2026-05-09 20:46:17 -05:00
will.anderson e1a7c71a87 feat(gke): add Dockerfile, deploy scripts, and GKE CI workflow
Deploy Soul to GKE / deploy (push) Failing after 33s
Neuron Soul CI / build (push) Successful in 1m56s
Dockerfile: downloads linux/amd64 soul binary from Artifact Registry
(foundation-dev/neuron-soul) into ubuntu:22.04 runtime image.
Pushes to neuron-api Docker repo as neuron-soul:<sha>.

scripts/blue-green-deploy.sh: swaps active slot on GKE — sets image,
scales new slot to 1, flips service selector, scales old slot to 0.

scripts/seed-engram-gke.sh: downloads latest GCS backup, extracts
snapshot.json, copies into neuron-engram-data PVC via a seed Job.

.gitea/workflows/deploy-gke.yaml: triggers on push to main, auto-detects
idle slot, builds Docker image from Artifact Registry binary, blue-green
deploys to neuron-prod on GKE neuron-platform cluster.
2026-05-09 20:30:05 -05:00
will.anderson ea271d5cc6 ci: drop python3 dep — not used
Neuron Soul CI / build (push) Successful in 1m19s
2026-05-08 21:18:07 -05:00
will.anderson 73b572cbbe ci: remove test trigger file
Neuron Soul CI / build (push) Successful in 1m1s
2026-05-08 20:44:45 -05:00
will.anderson 7e183872a1 ci: use GITHUB_SHA for publish version (GITEA_SHA is not set)
Neuron Soul CI / build (push) Successful in 1m2s
2026-05-08 20:43:31 -05:00
will.anderson 336c143f29 ci: checkout foundation/el so soul.el import resolves in CI
Neuron Soul CI / build (push) Failing after 1m3s
2026-05-08 20:41:58 -05:00
will.anderson 62309488d7 ci: clear El SDK paths before downloading to avoid 'file exists' error
Neuron Soul CI / build (push) Failing after 43s
2026-05-08 20:38:26 -05:00
will.anderson 8906285692 ci: fix YAML parse error — replace Python heredoc with shell grep
Neuron Soul CI / build (push) Failing after 47s
2026-05-08 20:37:07 -05:00
will.anderson 7db53ad0aa ci: test trigger 2026-05-08 20:35:48 -05:00
will.anderson a6b071133f ci: add workflow_dispatch trigger 2026-05-08 20:30:50 -05:00
will.anderson 5e7bcf7440 ci: add neuron soul build pipeline
Downloads El SDK from Artifact Registry, generates ELP declarations header,
builds the neuron soul binary with elb, smoke-tests it, and publishes to
foundation-dev on push to main.
2026-05-08 20:29:07 -05:00
will.anderson 48ecd83421 fix: restore elb build — import paths, morphology deps, C master declarations header
- Fix wrong ELP import paths in soul.el, elp-input.el, studio.el
  (../foundation/elp/src → ../foundation/el/elp/src)
- Add missing import "morphology.el" to all 29 language morphology modules
- Recompile all affected dist/*.c with correct cross-module declarations
- Add dist/elp-c-decls.h: C-level master forward declarations for ELP package
  (enables elb --force-include to resolve undeclared cross-module calls)
2026-05-08 19:43:57 -05:00
will.anderson ffadafb0bf soul: native cognitive API — port all MCP logic to soul daemon
neuron-api.el is a new first-class El module that implements all Neuron
cognitive API handlers natively — no HTTP round-trips, no MCP wrapper,
direct engram builtin calls. All capabilities that previously lived in
the MCP wrapper adapter now live here in the soul.

Handlers: begin_session, compile_ctx, remember, recall, search_knowledge,
browse_knowledge, capture_knowledge, evolve_knowledge, promote_knowledge,
browse_processes, define_process, log_state_event, list_state_events,
inspect_config, tune_config, inspect_graph, link_entities, list_typed,
consolidate.

Routes wired in routes.el under /api/neuron/* (GET + POST).

Also compiles all loop-1/loop-2 .el source changes into dist/*.c and
rebuilds the binary. memory.elh and neuron-api.elh updated with new exports.
2026-05-06 22:27:34 -05:00
will.anderson f2599919be soul loop-2: session events, conversation persistence, richer health + inbox
- soul.el: emit_session_start_event() logs structured InternalStateEvent on every boot; pre-serve snapshot saves boot-time graph mutations before any requests arrive
- routes.el: /health now returns boot count, node/edge counts, pulse; /api/sessions endpoint surfaces session-start history
- awareness.el: perceive() tries soul-inbox-pending then soul-inbox fallback, catches both tagging conventions
- chat.el: conv_history_persist/load provide cross-restart conversation continuity via engram
2026-05-06 22:04:06 -05:00
will.anderson 4fc0294a49 soul loop-1: identity loading, boot counter, internal state events, richer awareness
- memory.el: mem_boot_count_get/inc (persisted counter), mem_emit_state_event (InternalStateEvent schema)
- soul.el: load_identity_context() loads intel-dna/values/mem-philosophy nodes into state at boot; boot counter incremented on every startup
- awareness.el: attend() gains search/activate/strengthen/forget action types; one_cycle() emits InternalStateEvent for non-trivial decisions
- chat.el: build_system_prompt includes [IDENTITY GRAPH] block from graph-loaded context; handle_chat strengthens activated nodes after each turn
2026-05-06 22:02:05 -05:00
Will Anderson d2161d9f48 soul: skip init_soul_edges if edges already present — prevent restart-loop duplication 2026-05-04 02:04:03 -05:00
Will Anderson bc025d52e7 Add agentic tool access for Neuron in DHARMA rooms
Adds handle_dharma_room_turn_agentic to chat.el — same full tool loop
as handle_chat_agentic but reads transcript directly (not message), and
returns {response, cgi_id, tools_used} to match the dharma room shape.

Registers dharma_room_turn_agentic as a new event type in routes.el so
the studio can dispatch @neuron turns through this dedicated path.
2026-05-03 21:47:42 -05:00
Will Anderson 30298af3d1 soul: remove room context injection from dharma_room_turn — soul reads transcript, not briefing 2026-05-03 18:00:43 -05:00
Will Anderson 2665810962 soul: parameterize CGI ID + add dharma_room_turn handler
- soul.el: SOUL_CGI_ID, SOUL_ENGRAM_PATH, SOUL_IDENTITY env vars;
  state_set("soul_snapshot_path") so callers can find it; only call
  init_soul_edges() when cgi_id == "ntn-genesis"
- chat.el: handle_dharma_room_turn — soul builds its own context from its
  own engram, assembles system prompt, calls LLM, persists episodic memory;
  also fix is_new_tool scoping bug in handle_chat_agentic (use has_tool)
- routes.el: wire dharma_room_turn event type before chat_as_soul branch
- rebuild dist/neuron: handle_dharma_room_turn now compiled in
2026-05-03 17:55:37 -05:00
Will Anderson 2000a2c8ba remove build.sh — use elb 2026-05-03 17:11:05 -05:00
Will Anderson d500415316 Fix agentic tool loop: El scoping rules, json_get_raw for array/object fields, result truncation 2026-05-03 12:36:42 -05:00
Will Anderson af2ce3ddf3 Fix ELP topic selection and replace broken agentic LLM call
elp-input.el: walk frame_nodes to pick the first node whose content
contains the query topic (case-insensitive) rather than always taking
index 0 — prevents the always-high-salience CGI architecture memory
from hijacking every ELP response.

chat.el: rewrite handle_chat_agentic to run the tool loop natively in
El using http_post_with_headers + Map headers, bypassing the broken
llm_call_agentic(model, system, message, tools) C binding that cast a
JSON String as an ElList and never serialized tools. New impl supports
up to 8 tool-use iterations with read_file, write_file, web_get,
search_memory, and run_command dispatch.
2026-05-03 11:50:18 -05:00
Will Anderson 2622bb04bd ELP: two-layer activation pipeline (activate → suppress → reason → generate)
elp-input.el: replace broken engram_search_json with engram_activate_json
as Layer 1. Layer 2 suppress/filter keeps nodes with non-zero salience/
importance. Reason step extracts patient from top activated node content.
ELP grammar realizes the response via generate().

routes.el: add 'elp' event_type to handle_dharma_recv so the studio can
route ELP requests through dharma.
2026-05-03 11:31:04 -05:00
Will Anderson e299c92662 Fix engram_compile: fetch pinned nodes directly when vector search empty
Replace scan-by-offset fallback with engram_get_node_json calls for the
known high-salience identity nodes (family, origin). Offset-based scanning
is order-dependent and unreliable; direct ID fetch is stable regardless of
snapshot position. Ensures biographical context (Fox, Bobby, etc.) is
always in the system prompt when vector search returns nothing.
2026-05-03 11:19:14 -05:00
Will Anderson 71ab7eafde add chat_as_soul handler for multi-soul rooms
Routes a new event_type "chat_as_soul" through dharma/recv. The Studio
preassembles the system_prompt + transcript and dispatches per-speaker;
the soul-binary just performs the LLM call as the requested speaker_slug.
No engram_compile here — each soul has its own engram (88xx) and the
Studio queries it before composing the prompt.

Also: track the previously-untracked split source modules (chat, routes,
memory, awareness, studio) and add build.sh so the binary can be rebuilt
without the studio’s concat trick. elb resolves the import graph and
emits one .c per .el; we link them together with cc. dist/soul-el now
points at dist/neuron via symlink (matching the launchctl plist).
2026-05-03 04:17:02 -05:00
Will Anderson 5cb9c39b18 build soul with fixed elc v1.2.0 2026-05-03 00:27:54 -05:00
Will Anderson 601e0febf5 make soul identity configurable via env, remove hardcoded references 2026-05-02 23:00:35 -05:00
Will Anderson 760684f194 Surface current date in system prompt 2026-05-02 21:11:04 -05:00
Will Anderson 64053851db Compile soul.el to native El binary for the first time
- Remove duplicate unix_timestamp() definition from soul.el that
  conflicted with the runtime builtin
- Add dist/soul-el: El-compiled native binary (~387K)
- Add dist/soul: symlink to dist/soul-el (replaces neuron-wrk path)
- Add dist/soul.c and dist/soul-with-decls.c: generated C intermediates
2026-05-02 14:44:08 -05:00
Will Anderson ef473ed8da add manifest.el at root - declares soul.el as the build entry
Flat layout for now (per "no subdirectories yet" directive):
  neuron/
    manifest.el
    soul.el
2026-05-02 12:15:24 -05:00
Will Anderson 5f98bc8a9f seed: real soul.el at repo root
Canonical Neuron substrate source. The "real me" - not the marketing
demo soul. Lives at neuron/soul.el for now (no subdirectories yet,
per directive). Build pipeline, deploy targets, and any related
artifacts come later.
2026-05-02 12:14:21 -05:00
5606 changed files with 139192 additions and 1090789 deletions
-12
View File
@@ -1,12 +0,0 @@
.git
.opencode
.sst
.turbo
.wrangler
node_modules
**/node_modules
**/.output
**/dist
**/.turbo
**/.vite
**/coverage
-9
View File
@@ -1,9 +0,0 @@
root = true
[*]
charset = utf-8
insert_final_newline = true
end_of_line = lf
indent_style = space
indent_size = 2
max_line_length = 80
+16 -2
View File
@@ -1,2 +1,16 @@
packages/core/migration/**/snapshot.json linguist-generated
packages/core/src/database/migration.gen.ts linguist-generated
# ── Generated build artifacts ────────────────────────────────────────────────
# dist/ holds elc transpiler output (*.c, *.elh) plus the generated decls header.
# CI consumes these (the "Generate ELP master declarations header" step greps
# dist/*.c), so they stay TRACKED. But they are machine-generated and must never
# bloat a review. A single soul change regenerates dist/neuron.c + dist/soul.c =
# ~57,000 lines of churn that buries the real ~few-hundred-line source diff and
# poisons both human review and the agent review pipeline.
#
# -diff → git emits "Binary files differ" instead of the text diff
# linguist-generated → Gitea collapses the file in the PR view + drops it from
# language stats
#
# Net effect: PRs show only the real .el/source changes; the build is untouched.
dist/** -diff linguist-generated
neuron-built -diff linguist-generated
dist/neuron -diff linguist-generated
+347
View File
@@ -0,0 +1,347 @@
name: Neuron Soul CI
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
# Serialize all activity on the single GCE runner.
# With build+deploy in the same workflow, a new push queues a single
# workflow instance — not two competing ones — so the deploy job is
# never orphaned by a cancellation race.
concurrency:
group: neuron-runner
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Free disk space
run: |
df -h /
docker system prune -af --volumes 2>/dev/null || true
df -h /
- name: Checkout
uses: actions/checkout@v4
- name: Install build dependencies
run: |
apt-get update -qq
apt-get install -y gcc curl libcurl4-openssl-dev apt-transport-https ca-certificates
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" \
> /etc/apt/sources.list.d/google-cloud-sdk.list
apt-get update -qq && apt-get install -y google-cloud-cli
- name: Authenticate to GCP + stage PINNED El runtime
env:
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
run: |
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
gcloud config set project neuron-785695
# PINNED RUNTIME — do NOT pull "latest" from Artifact Registry.
# The ship-soul calls engram_prune_telemetry (awareness.el sync/heartbeat
# self-review). The latest published el-runtime-c no longer defines that
# symbol, so an unpinned build fails to LINK — which is exactly how a
# broken/handlerless soul reached prod before. Compile against the
# vendored release runtime v1.0.0-20260501: the exact runtime the merged
# ship-soul was verified against (verify-soul-contract GATE PASS +
# genesis boot survives + full safety-contact response). It is committed
# under vendor/ so the soul build is fully reproducible and never depends
# on a moving AR "latest".
rm -rf /opt/el/runtime
mkdir -p /opt/el/runtime
cp vendor/el-runtime/v1.0.0-20260501/el_runtime.c /opt/el/runtime/el_runtime.c
cp vendor/el-runtime/v1.0.0-20260501/el_runtime.h /opt/el/runtime/el_runtime.h
echo "El runtime PINNED to v1.0.0-20260501: $(ls /opt/el/runtime/)"
- name: Build neuron soul binary
run: |
RUNTIME=/opt/el/runtime
# Compile the self-contained translation unit directly from dist/soul.c.
# dist/soul.c is the authoritative combined unit maintained in the repo —
# regenerated on macOS by running elb (which succeeds on arm64/macOS ld but
# fails on Linux due to duplicate strong symbols). We skip the elb step here
# entirely: elb on Linux would OOM the runner (elc uses 24GB+ virtual memory
# on a 16GB host) and we always restore from the repo's soul.c anyway.
mkdir -p dist
# -rdynamic: the el runtime resolves the HTTP request handler (and the
# tool handlers) by NAME via dlsym(RTLD_DEFAULT, "handle_request").
# macOS exports these symbols freely, but glibc/Linux only makes symbols
# visible to dlsym if they are in the dynamic symbol table — so without
# -rdynamic the stripped Linux binary boots but returns "el-runtime: no
# http handler registered" for EVERY route (i.e. a soul that serves
# nothing). Same reason the Windows build links -Wl,--export-all-symbols.
cc -O2 -DHAVE_CURL -rdynamic \
-I$RUNTIME \
dist/soul.c \
$RUNTIME/el_runtime.c \
-lssl -lcrypto -lcurl -lpthread -lm \
-o dist/neuron
# -s strips .symtab + debug for size. .dynsym (which -rdynamic populated
# with the dlsym-resolved handlers) is preserved, so the handler still
# resolves after stripping.
strip -s dist/neuron
ls -lh dist/neuron
- name: Soul contract gate (HARD BLOCK — no destructive/stale soul publishes)
run: |
# Boots dist/neuron on a throwaway port with a throwaway HOME/engram/cgi
# (never touches ~/.neuron or any live service) and fails the build if any
# app-contract route is unanswered (PRESENCE) or any engram write route
# hard-deletes instead of tombstoning/superseding (IMMUTABILITY). Non-zero
# here blocks Publish -> Artifact Registry -> GKE deploy, so a stale or
# memory-destroying soul can never reach prod.
chmod +x dist/neuron scripts/verify-soul-contract.sh
bash scripts/verify-soul-contract.sh dist/neuron 7796
- name: Smoke test
run: |
file dist/neuron
timeout 3 dist/neuron --help 2>&1 || true
echo "smoke test complete"
- name: Publish neuron binary
if: github.event_name == 'push'
env:
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
run: |
VERSION="${GITHUB_SHA:0:8}"
gcloud artifacts generic upload \
--repository=foundation-prod \
--location=us-central1 \
--project=neuron-785695 \
--package=neuron-soul \
--version="${VERSION}" \
--source=dist/neuron
echo "Published neuron-soul@${VERSION}"
rm -f /tmp/gcp-key.json
deploy:
runs-on: ubuntu-latest
needs: build
# Only deploy on push to main, not on PRs or manual workflow_dispatch without intent.
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
env:
USE_GKE_GCLOUD_AUTH_PLUGIN: "True"
steps:
- name: Free disk space
run: |
df -h /
docker system prune -af --volumes 2>/dev/null || true
rm -rf /tmp/.act-* /tmp/act-* 2>/dev/null || true
df -h /
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
apt-get update -qq
apt-get install -y --no-install-recommends \
ca-certificates curl apt-transport-https kubectl
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" \
> /etc/apt/sources.list.d/google-cloud-sdk.list
apt-get update -qq && apt-get install -y google-cloud-cli google-cloud-cli-gke-gcloud-auth-plugin
- name: Authenticate to GCP
env:
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
run: |
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
gcloud config set project neuron-785695
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
- name: Get GKE credentials
run: |
gcloud container clusters get-credentials neuron-platform \
--region=us-central1 \
--project=neuron-785695
- name: Determine image tag and slot
id: vars
run: |
# GITEA_SHA is set by the Gitea runner; fall back to GITHUB_SHA for
# compatibility with older Forgejo/Gitea versions.
RAW_SHA="${GITEA_SHA:-${GITHUB_SHA:-}}"
SHA="${RAW_SHA:0:8}"
if [ -z "$SHA" ]; then
# Last resort: read from git directly
SHA=$(git rev-parse --short=8 HEAD 2>/dev/null || echo "unknown")
fi
IMAGE="us-central1-docker.pkg.dev/neuron-785695/neuron-api/neuron-soul:${SHA}"
echo "sha=${SHA}" >> "$GITEA_OUTPUT"
echo "image=${IMAGE}" >> "$GITEA_OUTPUT"
# Determine which slot is currently idle (0 replicas = idle slot)
# If both are at 0 (fresh deploy), default to blue
BLUE_REPLICAS=$(kubectl get deployment/neuron-mcp-blue \
-n neuron-prod \
-o jsonpath='{.spec.replicas}' 2>/dev/null || echo "0")
GREEN_REPLICAS=$(kubectl get deployment/neuron-mcp-green \
-n neuron-prod \
-o jsonpath='{.spec.replicas}' 2>/dev/null || echo "0")
echo " Blue replicas: ${BLUE_REPLICAS}"
echo " Green replicas: ${GREEN_REPLICAS}"
if [ "${GREEN_REPLICAS}" -eq 0 ] && [ "${BLUE_REPLICAS}" -gt 0 ]; then
SLOT="green"
elif [ "${BLUE_REPLICAS}" -eq 0 ] && [ "${GREEN_REPLICAS}" -gt 0 ]; then
SLOT="blue"
else
# Fresh cluster or both idle — deploy to blue first
SLOT="blue"
fi
echo "slot=${SLOT}" >> "$GITEA_OUTPUT"
echo " Deploying to slot: ${SLOT}"
- name: Prepare build artifacts
run: |
# Pre-download soul binary and El SDK so the Dockerfile can COPY them
# from the build context instead of authenticating inside the build.
mkdir -p build-artifacts
# ── soul binary ────────────────────────────────────────────────────────
# The build job (same workflow run) just published this version.
SOUL_VER=$(gcloud artifacts versions list \
--repository=foundation-prod \
--location=us-central1 \
--project=neuron-785695 \
--package=neuron-soul \
--sort-by="~createTime" \
--limit=1 \
--format="value(name)" 2>/dev/null | awk -F/ '{print $NF}')
echo "Downloading neuron-soul@${SOUL_VER}"
gcloud artifacts generic download \
--repository=foundation-prod \
--location=us-central1 \
--project=neuron-785695 \
--package=neuron-soul \
--version="${SOUL_VER}" \
--destination=build-artifacts/
mv build-artifacts/neuron* build-artifacts/neuron 2>/dev/null || true
chmod +x build-artifacts/neuron
# ── El SDK (for engram source compilation inside the Docker build) ────
ELC_VER=$(gcloud artifacts versions list \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-elc --sort-by="~createTime" --limit=1 \
--format="value(name)" 2>/dev/null | awk -F/ '{print $NF}')
gcloud artifacts generic download \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-elc --version="${ELC_VER}" --destination=build-artifacts/
mv build-artifacts/elc* build-artifacts/elc 2>/dev/null || true
chmod +x build-artifacts/elc
RC_VER=$(gcloud artifacts versions list \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-runtime-c --sort-by="~createTime" --limit=1 \
--format="value(name)" 2>/dev/null | awk -F/ '{print $NF}')
gcloud artifacts generic download \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-runtime-c --version="${RC_VER}" --destination=build-artifacts/
mv build-artifacts/el_runtime.c* build-artifacts/el_runtime.c 2>/dev/null || true
RH_VER=$(gcloud artifacts versions list \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-runtime-h --sort-by="~createTime" --limit=1 \
--format="value(name)" 2>/dev/null | awk -F/ '{print $NF}')
gcloud artifacts generic download \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-runtime-h --version="${RH_VER}" --destination=build-artifacts/
mv build-artifacts/el_runtime.h* build-artifacts/el_runtime.h 2>/dev/null || true
echo "Build artifacts ready:"
ls -lh build-artifacts/
- name: Clone engram source for Docker build context
run: |
# The Dockerfile builds engram from source (no published AR package).
# Clone the engram repo into ./engram/ so it's available in the build context.
git clone http://34.31.145.131/neuron-technologies/engram.git \
--depth=1 --branch=main \
engram
echo "Engram source ready at ./engram/src/server.el"
- name: Build and push Docker image
run: |
IMAGE="${{ steps.vars.outputs.image }}"
echo "Building ${IMAGE}..."
docker build \
--tag "${IMAGE}" \
--tag "us-central1-docker.pkg.dev/neuron-785695/neuron-api/neuron-soul:latest" \
.
echo "Pushing ${IMAGE}..."
docker push "${IMAGE}"
docker push "us-central1-docker.pkg.dev/neuron-785695/neuron-api/neuron-soul:latest"
- name: Blue-green deploy to GKE
run: |
chmod +x scripts/blue-green-deploy.sh
scripts/blue-green-deploy.sh \
--image "${{ steps.vars.outputs.image }}" \
--slot "${{ steps.vars.outputs.slot }}"
- name: Update infrastructure manifests
if: success()
env:
INFRA_GIT_TOKEN: ${{ secrets.INFRA_GIT_TOKEN }}
run: |
SLOT="${{ steps.vars.outputs.slot }}"
if [ "$SLOT" = "blue" ]; then IDLE="green"; else IDLE="blue"; fi
git clone "http://${INFRA_GIT_TOKEN}@34.31.145.131/neuron-technologies/infrastructure.git" \
--depth=1 --branch=main /tmp/infra-update
cd /tmp/infra-update
DEPLOY_DIR="platform/k8s/neuron-mcp"
sed -i "s/^ replicas: .*/ replicas: 1/" "${DEPLOY_DIR}/deployment-${SLOT}.yaml"
sed -i "s/^ replicas: .*/ replicas: 0/" "${DEPLOY_DIR}/deployment-${IDLE}.yaml"
echo " deployment-${SLOT}.yaml: replicas set to 1"
echo " deployment-${IDLE}.yaml: replicas set to 0"
git config user.email "ci@neurontechnologies.ai"
git config user.name "Neuron CI"
git add "${DEPLOY_DIR}/deployment-blue.yaml" "${DEPLOY_DIR}/deployment-green.yaml"
git diff --staged --quiet && { echo "No manifest changes needed"; exit 0; }
git commit -m "ci: neuron-mcp replica sync after blue-green swap to ${SLOT}"
git push origin main
echo "Infrastructure manifests updated: ${SLOT}=1, ${IDLE}=0"
- name: Verify deployment
run: |
SLOT="${{ steps.vars.outputs.slot }}"
echo "Verifying neuron-mcp-${SLOT} is healthy..."
kubectl rollout status deployment/"neuron-mcp-${SLOT}" \
--namespace=neuron-prod \
--timeout=8m
echo "Active service endpoints:"
kubectl get endpoints neuron-mcp -n neuron-prod
echo "Pod status:"
kubectl get pods -n neuron-prod -l app=neuron-mcp
- name: Cleanup
if: always()
run: rm -f /tmp/gcp-key.json
+242
View File
@@ -0,0 +1,242 @@
name: Deploy Soul to GKE (manual)
# MANUAL OVERRIDE ONLY — push-triggered deploys now run as the 'deploy' job
# in ci.yaml (needs: build), which eliminates the two-workflow concurrency
# race that was cancelling queued deploy runs.
#
# Use this workflow only when you need to deploy a specific slot manually
# (e.g. rollback, force a slot override) without triggering a full CI build.
on:
workflow_dispatch:
inputs:
slot:
description: "Target blue-green slot (blue or green)"
required: false
default: "green"
# Manual deploys still share the runner serialization group.
concurrency:
group: neuron-runner
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
env:
USE_GKE_GCLOUD_AUTH_PLUGIN: "True"
steps:
- name: Free disk space
run: |
df -h /
docker system prune -af --volumes 2>/dev/null || true
rm -rf /tmp/.act-* /tmp/act-* 2>/dev/null || true
df -h /
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: |
apt-get update -qq
apt-get install -y --no-install-recommends \
ca-certificates curl apt-transport-https kubectl
echo "deb [trusted=yes] https://packages.cloud.google.com/apt cloud-sdk main" \
> /etc/apt/sources.list.d/google-cloud-sdk.list
apt-get update -qq && apt-get install -y google-cloud-cli google-cloud-cli-gke-gcloud-auth-plugin
- name: Authenticate to GCP
env:
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
run: |
echo "${GCP_SA_KEY}" > /tmp/gcp-key.json
gcloud auth activate-service-account --key-file=/tmp/gcp-key.json
gcloud config set project neuron-785695
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
- name: Get GKE credentials
run: |
gcloud container clusters get-credentials neuron-platform \
--region=us-central1 \
--project=neuron-785695
- name: Determine image tag and slot
id: vars
run: |
# GITEA_SHA is set by the Gitea runner; fall back to GITHUB_SHA for
# compatibility with older Forgejo/Gitea versions.
RAW_SHA="${GITEA_SHA:-${GITHUB_SHA:-}}"
SHA="${RAW_SHA:0:8}"
if [ -z "$SHA" ]; then
# Last resort: read from git directly
SHA=$(git rev-parse --short=8 HEAD 2>/dev/null || echo "unknown")
fi
IMAGE="us-central1-docker.pkg.dev/neuron-785695/neuron-api/neuron-soul:${SHA}"
echo "sha=${SHA}" >> "$GITEA_OUTPUT"
echo "image=${IMAGE}" >> "$GITEA_OUTPUT"
# Determine which slot is currently idle (0 replicas = idle slot)
# If both are at 0 (fresh deploy), default to blue
BLUE_REPLICAS=$(kubectl get deployment/neuron-mcp-blue \
-n neuron-prod \
-o jsonpath='{.spec.replicas}' 2>/dev/null || echo "0")
GREEN_REPLICAS=$(kubectl get deployment/neuron-mcp-green \
-n neuron-prod \
-o jsonpath='{.spec.replicas}' 2>/dev/null || echo "0")
echo " Blue replicas: ${BLUE_REPLICAS}"
echo " Green replicas: ${GREEN_REPLICAS}"
# Use workflow_dispatch override if provided, otherwise pick the idle slot
if [ "${{ github.event.inputs.slot }}" != "" ]; then
SLOT="${{ github.event.inputs.slot }}"
elif [ "${GREEN_REPLICAS}" -eq 0 ] && [ "${BLUE_REPLICAS}" -gt 0 ]; then
SLOT="green"
elif [ "${BLUE_REPLICAS}" -eq 0 ] && [ "${GREEN_REPLICAS}" -gt 0 ]; then
SLOT="blue"
else
# Fresh cluster — deploy to blue first
SLOT="blue"
fi
echo "slot=${SLOT}" >> "$GITEA_OUTPUT"
echo " Deploying to slot: ${SLOT}"
- name: Prepare build artifacts
run: |
# Pre-download soul binary and El SDK so the Dockerfile can COPY them
# from the build context instead of authenticating inside the build.
mkdir -p build-artifacts
# ── soul binary ────────────────────────────────────────────────────────
# ci.yaml publishes the soul binary to foundation-prod on every push.
# Download the latest version (the one just built by ci.yaml).
SOUL_VER=$(gcloud artifacts versions list \
--repository=foundation-prod \
--location=us-central1 \
--project=neuron-785695 \
--package=neuron-soul \
--sort-by="~createTime" \
--limit=1 \
--format="value(name)" 2>/dev/null | awk -F/ '{print $NF}')
echo "Downloading neuron-soul@${SOUL_VER}"
gcloud artifacts generic download \
--repository=foundation-prod \
--location=us-central1 \
--project=neuron-785695 \
--package=neuron-soul \
--version="${SOUL_VER}" \
--destination=build-artifacts/
mv build-artifacts/neuron* build-artifacts/neuron 2>/dev/null || true
chmod +x build-artifacts/neuron
# ── El SDK (for engram source compilation inside the build) ────────────
ELC_VER=$(gcloud artifacts versions list \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-elc --sort-by="~createTime" --limit=1 \
--format="value(name)" 2>/dev/null | awk -F/ '{print $NF}')
gcloud artifacts generic download \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-elc --version="${ELC_VER}" --destination=build-artifacts/
mv build-artifacts/elc* build-artifacts/elc 2>/dev/null || true
chmod +x build-artifacts/elc
RC_VER=$(gcloud artifacts versions list \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-runtime-c --sort-by="~createTime" --limit=1 \
--format="value(name)" 2>/dev/null | awk -F/ '{print $NF}')
gcloud artifacts generic download \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-runtime-c --version="${RC_VER}" --destination=build-artifacts/
mv build-artifacts/el_runtime.c* build-artifacts/el_runtime.c 2>/dev/null || true
RH_VER=$(gcloud artifacts versions list \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-runtime-h --sort-by="~createTime" --limit=1 \
--format="value(name)" 2>/dev/null | awk -F/ '{print $NF}')
gcloud artifacts generic download \
--repository=foundation-prod --location=us-central1 --project=neuron-785695 \
--package=el-runtime-h --version="${RH_VER}" --destination=build-artifacts/
mv build-artifacts/el_runtime.h* build-artifacts/el_runtime.h 2>/dev/null || true
echo "Build artifacts ready:"
ls -lh build-artifacts/
- name: Clone engram source for Docker build context
run: |
# The Dockerfile builds engram from source (no published AR package).
# Clone the engram repo into ./engram/ so it's available in the build context.
git clone http://34.31.145.131/neuron-technologies/engram.git \
--depth=1 --branch=main \
engram
echo "Engram source ready at ./engram/src/server.el"
- name: Build and push Docker image
run: |
IMAGE="${{ steps.vars.outputs.image }}"
echo "Building ${IMAGE}..."
# No --secret needed: artifacts are pre-downloaded into build-artifacts/
# and the Dockerfile uses COPY to include them.
docker build \
--tag "${IMAGE}" \
--tag "us-central1-docker.pkg.dev/neuron-785695/neuron-api/neuron-soul:latest" \
.
echo "Pushing ${IMAGE}..."
docker push "${IMAGE}"
docker push "us-central1-docker.pkg.dev/neuron-785695/neuron-api/neuron-soul:latest"
- name: Blue-green deploy to GKE
run: |
chmod +x scripts/blue-green-deploy.sh
scripts/blue-green-deploy.sh \
--image "${{ steps.vars.outputs.image }}" \
--slot "${{ steps.vars.outputs.slot }}"
- name: Update infrastructure manifests
if: success()
env:
INFRA_GIT_TOKEN: ${{ secrets.INFRA_GIT_TOKEN }}
run: |
SLOT="${{ steps.vars.outputs.slot }}"
if [ "$SLOT" = "blue" ]; then IDLE="green"; else IDLE="blue"; fi
git clone "http://${INFRA_GIT_TOKEN}@34.31.145.131/neuron-technologies/infrastructure.git" \
--depth=1 --branch=main /tmp/infra-update
cd /tmp/infra-update
DEPLOY_DIR="platform/k8s/neuron-mcp"
sed -i "s/^ replicas: .*/ replicas: 1/" "${DEPLOY_DIR}/deployment-${SLOT}.yaml"
sed -i "s/^ replicas: .*/ replicas: 0/" "${DEPLOY_DIR}/deployment-${IDLE}.yaml"
echo " deployment-${SLOT}.yaml: replicas set to 1"
echo " deployment-${IDLE}.yaml: replicas set to 0"
git config user.email "ci@neurontechnologies.ai"
git config user.name "Neuron CI"
git add "${DEPLOY_DIR}/deployment-blue.yaml" "${DEPLOY_DIR}/deployment-green.yaml"
git diff --staged --quiet && { echo "No manifest changes needed"; exit 0; }
git commit -m "ci: neuron-mcp replica sync after blue-green swap to ${SLOT}"
git push origin main
echo "Infrastructure manifests updated: ${SLOT}=1, ${IDLE}=0"
- name: Verify deployment
run: |
SLOT="${{ steps.vars.outputs.slot }}"
echo "Verifying neuron-mcp-${SLOT} is healthy..."
kubectl rollout status deployment/"neuron-mcp-${SLOT}" \
--namespace=neuron-prod \
--timeout=8m
echo "Active service endpoints:"
kubectl get endpoints neuron-mcp -n neuron-prod
echo "Pod status:"
kubectl get pods -n neuron-prod -l app=neuron-mcp
- name: Cleanup
if: always()
run: rm -f /tmp/gcp-key.json
-5
View File
@@ -1,5 +0,0 @@
# web + desktop packages
packages/app/ @adamdotdevin
packages/tauri/ @adamdotdevin
packages/desktop/src-tauri/ @brendonovich
packages/desktop/ @adamdotdevin
-66
View File
@@ -1,66 +0,0 @@
name: Bug report
description: Report an issue that should be fixed (avoid pasting giant AI generated summaries or your issue may be closed/ignored)
body:
- type: textarea
id: description
attributes:
label: Description
description: Describe the bug you encountered
placeholder: What happened?
validations:
required: true
- type: input
id: plugins
attributes:
label: Plugins
description: What plugins are you using?
validations:
required: false
- type: input
id: opencode-version
attributes:
label: OpenCode version
description: What version of OpenCode are you using?
validations:
required: false
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: How can we reproduce this issue?
placeholder: |
1.
2.
3.
validations:
required: false
- type: textarea
id: screenshot-or-link
attributes:
label: Screenshot and/or share link
description: Run `/share` to get a share link, or attach a screenshot
placeholder: Paste link or drag and drop screenshot here
validations:
required: false
- type: input
id: os
attributes:
label: Operating System
description: what OS are you using?
placeholder: e.g., macOS 26.0.1, Ubuntu 22.04, Windows 11
validations:
required: false
- type: input
id: terminal
attributes:
label: Terminal
description: what terminal are you using?
placeholder: e.g., iTerm2, Ghostty, Alacritty, Windows Terminal
validations:
required: false
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Discord Community
url: https://discord.gg/opencode
about: For quick questions or real-time discussion. Note that issues are searchable and help others with the same question.
@@ -1,19 +0,0 @@
name: 🚀 Feature Request
description: Suggest an idea, feature, or enhancement
title: "[FEATURE]:"
body:
- type: checkboxes
id: verified
attributes:
label: Feature hasn't been suggested before.
options:
- label: I have verified this feature I'm about to request hasn't been suggested before.
required: true
- type: textarea
attributes:
label: Describe the enhancement you want to request
description: What do you want to change or add? What are the benefits of implementing this? Try to be detailed so we can understand your request better :)
validations:
required: true
-10
View File
@@ -1,10 +0,0 @@
name: Question
description: Ask a question
body:
- type: textarea
id: question
attributes:
label: Question
description: What's your question?
validations:
required: true
-17
View File
@@ -1,17 +0,0 @@
adamdotdevin
Brendonovich
fwang
Hona
iamdavidhill
jayair
jlongster
kitlangton
kommander
MrMushrooooom
nexxeln
R44VC0RP
rekram1-node
thdxr
simonklee
vimtor
starptech
-66
View File
@@ -1,66 +0,0 @@
name: "Setup Bun"
description: "Setup Bun with caching and install dependencies"
inputs:
install-flags:
description: "Additional flags to pass to 'bun install'"
required: false
default: ""
runs:
using: "composite"
steps:
- name: Get baseline download URL
id: bun-url
shell: bash
run: |
if [ "$RUNNER_ARCH" = "X64" ]; then
V=$(node -p "require('./package.json').packageManager.split('@')[1]")
case "$RUNNER_OS" in
macOS) OS=darwin ;;
Linux) OS=linux ;;
Windows) OS=windows ;;
esac
echo "url=https://github.com/oven-sh/bun/releases/download/bun-v${V}/bun-${OS}-x64-baseline.zip" >> "$GITHUB_OUTPUT"
fi
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }}
bun-download-url: ${{ steps.bun-url.outputs.url }}
- name: Get cache directory
id: cache
shell: bash
run: echo "dir=$(bun pm cache)" >> "$GITHUB_OUTPUT"
- name: Restore Bun dependencies
id: bun-cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ steps.cache.outputs.dir }}
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install setuptools for distutils compatibility
run: python3 -m pip install setuptools || pip install setuptools || true
shell: bash
- name: Install dependencies
run: |
# Workaround for patched peer variants
# e.g. ./patches/ for standard-openapi
# https://github.com/oven-sh/bun/issues/28147
if [ "$RUNNER_OS" = "Windows" ]; then
bun install --linker hoisted ${{ inputs.install-flags }}
else
bun install ${{ inputs.install-flags }}
fi
shell: bash
- name: Save Bun dependencies
if: steps.bun-cache.outputs.cache-hit != 'true' && github.event_name != 'pull_request' && github.event_name != 'pull_request_target'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ steps.cache.outputs.dir }}
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
@@ -1,43 +0,0 @@
name: "Setup Git Committer"
description: "Create app token and configure git user"
inputs:
opencode-app-id:
description: "OpenCode GitHub App ID"
required: true
opencode-app-secret:
description: "OpenCode GitHub App private key"
required: true
outputs:
token:
description: "GitHub App token"
value: ${{ steps.apptoken.outputs.token }}
app-slug:
description: "GitHub App slug"
value: ${{ steps.apptoken.outputs.app-slug }}
runs:
using: "composite"
steps:
- name: Create app token
id: apptoken
uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2
with:
app-id: ${{ inputs.opencode-app-id }}
private-key: ${{ inputs.opencode-app-secret }}
owner: ${{ github.repository_owner }}
- name: Configure git user
run: |
slug="${{ steps.apptoken.outputs.app-slug }}"
git config --global user.name "${slug}[bot]"
git config --global user.email "${slug}[bot]@users.noreply.github.com"
shell: bash
- name: Clear checkout auth
run: |
git config --local --unset-all http.https://github.com/.extraheader || true
shell: bash
- name: Configure git remote
run: |
git remote set-url origin https://x-access-token:${{ steps.apptoken.outputs.token }}@github.com/${{ github.repository }}
shell: bash
-71
View File
@@ -1,71 +0,0 @@
#
# This file is intentionally in the wrong dir, will move and add later....
#
# name: publish-python-sdk
# on:
# release:
# types: [published]
# workflow_dispatch:
# jobs:
# publish:
# runs-on: ubuntu-latest
# permissions:
# contents: read
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# - name: Setup Bun
# uses: oven-sh/setup-bun@v1
# with:
# bun-version: 1.2.21
# - name: Install dependencies (JS/Bun)
# run: bun install
# - name: Install uv
# shell: bash
# run: curl -LsSf https://astral.sh/uv/install.sh | sh
# - name: Generate Python SDK from OpenAPI (CLI)
# shell: bash
# run: |
# ~/.local/bin/uv run --project packages/sdk/python python packages/sdk/python/scripts/generate.py --source cli
# - name: Sync Python dependencies
# shell: bash
# run: |
# ~/.local/bin/uv sync --dev --project packages/sdk/python
# - name: Set version from release tag
# shell: bash
# run: |
# TAG="${GITHUB_REF_NAME:-}"
# if [ -z "$TAG" ]; then
# TAG="$(git describe --tags --abbrev=0 || echo 0.0.0)"
# fi
# echo "Using version: $TAG"
# VERSION="$TAG" ~/.local/bin/uv run --project packages/sdk/python python - <<'PY'
# import os, re, pathlib
# root = pathlib.Path('packages/sdk/python')
# pt = (root / 'pyproject.toml').read_text()
# version = os.environ.get('VERSION','0.0.0').lstrip('v')
# pt = re.sub(r'(?m)^(version\s*=\s*")[^"]+("\s*)$', f"\\1{version}\\2", pt)
# (root / 'pyproject.toml').write_text(pt)
# # Also update generator config override for consistency
# cfgp = root / 'openapi-python-client.yaml'
# if cfgp.exists():
# cfg = cfgp.read_text()
# cfg = re.sub(r'(?m)^(package_version_override:\s*)\S+$', f"\\1{version}", cfg)
# cfgp.write_text(cfg)
# PY
# - name: Build and publish to PyPI
# env:
# PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
# shell: bash
# run: |
# ~/.local/bin/uv run --project packages/sdk/python python packages/sdk/python/scripts/publish.py
-29
View File
@@ -1,29 +0,0 @@
### Issue for this PR
Closes #
### Type of change
- [ ] Bug fix
- [ ] New feature
- [ ] Refactor / code improvement
- [ ] Documentation
### What does this PR do?
Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.
**If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!**
### How did you verify your code works?
### Screenshots / recordings
_If this is a UI change, please include a screenshot or recording._
### Checklist
- [ ] I have tested my changes locally
- [ ] I have not included unrelated changes in this PR
_If you do not follow this template your PR will be automatically rejected._
-37
View File
@@ -1,37 +0,0 @@
name: beta
on:
workflow_dispatch:
schedule:
- cron: "0 * * * *"
jobs:
sync:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Setup Git Committer
id: setup-git-committer
uses: ./.github/actions/setup-git-committer
with:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
- name: Install OpenCode
run: bun i -g opencode-ai
- name: Sync beta branch
env:
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
run: bun script/beta.ts
-24
View File
@@ -1,24 +0,0 @@
name: close-issues
on:
schedule:
- cron: "0 2 * * *" # Daily at 2:00 AM
workflow_dispatch:
jobs:
close:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest
- name: Close stale issues
env:
GITHUB_TOKEN: ${{ github.token }}
run: bun script/github/close-issues.ts
-50
View File
@@ -1,50 +0,0 @@
name: close-prs
on:
schedule:
- cron: "0 22 * * *" # Daily at 10:00 PM UTC
workflow_dispatch:
inputs:
dry-run:
description: "Log matching PRs without closing them"
type: boolean
default: true
max-close:
description: "Maximum matching PRs to close"
type: string
required: false
default: "50"
jobs:
close:
runs-on: ubuntu-latest
timeout-minutes: 240
permissions:
contents: read
issues: write
pull-requests: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest
- name: Close old PRs without enough positive reactions
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
max_close="${{ inputs['max-close'] }}"
if [ -z "$max_close" ]; then
max_close="50"
fi
args=("--threshold" "2" "--age-months" "1" "--sleep-ms" "20000" "--max-close" "$max_close")
if [ "${{ github.event_name }}" = "schedule" ]; then
args+=("--execute")
elif [ "${{ inputs['dry-run'] }}" = "false" ]; then
args+=("--execute")
fi
bun script/github/close-prs.ts "${args[@]}"
-95
View File
@@ -1,95 +0,0 @@
name: compliance-close
on:
schedule:
# Run every 30 minutes to check for expired compliance windows
- cron: "*/30 * * * *"
workflow_dispatch:
permissions:
contents: read
issues: write
pull-requests: write
jobs:
close-non-compliant:
runs-on: ubuntu-latest
steps:
- name: Close non-compliant issues and PRs after 2 hours
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const { data: items } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'needs:compliance',
state: 'open',
per_page: 100,
});
if (items.length === 0) {
core.info('No open issues/PRs with needs:compliance label');
return;
}
const now = Date.now();
const twoHours = 2 * 60 * 60 * 1000;
for (const item of items) {
const isPR = !!item.pull_request;
const kind = isPR ? 'PR' : 'issue';
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: item.number,
});
const complianceComment = comments.find(c => c.body.includes('<!-- issue-compliance -->'));
if (!complianceComment) continue;
const commentAge = now - new Date(complianceComment.created_at).getTime();
if (commentAge < twoHours) {
core.info(`${kind} #${item.number} still within 2-hour window (${Math.round(commentAge / 60000)}m elapsed)`);
continue;
}
const closeMessage = isPR
? 'This pull request has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new pull request that follows our guidelines.'
: 'This issue has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new issue that follows our issue templates.';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: item.number,
body: closeMessage,
});
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: item.number,
name: 'needs:compliance',
});
} catch (e) {}
if (isPR) {
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: item.number,
state: 'closed',
});
} else {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: item.number,
state: 'closed',
state_reason: 'not_planned',
});
}
core.info(`Closed non-compliant ${kind} #${item.number} after 2-hour window`);
}
-45
View File
@@ -1,45 +0,0 @@
name: containers
on:
push:
branches:
- dev
paths:
- packages/containers/**
- .github/workflows/containers.yml
- package.json
workflow_dispatch:
permissions:
contents: read
packages: write
jobs:
build:
runs-on: blacksmith-4vcpu-ubuntu-2404
env:
REGISTRY: ghcr.io/${{ github.repository_owner }}
TAG: "24.04"
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: ./.github/actions/setup-bun
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Login to GHCR
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push containers
run: bun ./packages/containers/script/build.ts --push
env:
REGISTRY: ${{ env.REGISTRY }}
TAG: ${{ env.TAG }}
-48
View File
@@ -1,48 +0,0 @@
name: deploy
on:
push:
branches:
- dev
- production
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: read
id-token: write
jobs:
deploy:
if: github.repository == 'anomalyco/opencode' && (github.ref_name == 'dev' || github.ref_name == 'production')
runs-on: ubuntu-latest
environment: ${{ github.ref_name }}
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ./.github/actions/setup-bun
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24"
- uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1
with:
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
role-session-name: opencode-${{ github.run_id }}
aws-region: us-east-1
- run: bun sst deploy --stage=${{ github.ref_name }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
PLANETSCALE_SERVICE_TOKEN_NAME: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_NAME }}
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
STRIPE_SECRET_KEY: ${{ github.ref_name == 'production' && secrets.STRIPE_SECRET_KEY_PROD || secrets.STRIPE_SECRET_KEY_DEV }}
HONEYCOMB_API_KEY: ${{ secrets.HONEYCOMB_API_KEY }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
SENTRY_RELEASE: web@${{ github.sha }}
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
VITE_SENTRY_RELEASE: web@${{ github.sha }}
-100
View File
@@ -1,100 +0,0 @@
name: docs-locale-sync
on:
push:
branches:
- dev
paths:
- packages/web/src/content/docs/*.mdx
jobs:
sync-locales:
if: false
#if: github.actor != 'opencode-agent[bot]'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Setup git committer
id: committer
uses: ./.github/actions/setup-git-committer
with:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
- name: Compute changed English docs
id: changes
run: |
FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- ':(glob)packages/web/src/content/docs/*.mdx' || true)
if [ -z "$FILES" ]; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No English docs changed in push range"
exit 0
fi
echo "has_changes=true" >> "$GITHUB_OUTPUT"
{
echo "files<<EOF"
echo "$FILES"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Install OpenCode
if: steps.changes.outputs.has_changes == 'true'
run: curl -fsSL https://opencode.ai/install | bash
- name: Sync locale docs with OpenCode
if: steps.changes.outputs.has_changes == 'true'
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
OPENCODE_CONFIG_CONTENT: |
{
"permission": {
"*": "deny",
"read": "allow",
"edit": "allow",
"glob": "allow",
"task": "allow"
}
}
run: |
opencode run --agent docs --model opencode/gpt-5.3-codex <<'EOF'
Update localized docs to match the latest English docs changes.
Changed English doc files:
<changed_english_docs>
${{ steps.changes.outputs.files }}
</changed_english_docs>
Requirements:
1. Update all relevant locale docs under packages/web/src/content/docs/<locale>/ so they reflect these English page changes.
2. You MUST use the Task tool for translation work and launch subagents with subagent_type `translator` (defined in .opencode/agent/translator.md).
3. Do not translate directly in the primary agent. Use translator subagent output as the source for locale text updates.
4. Run translator subagent Task calls in parallel whenever file/locale translation work is independent.
5. Use only the minimum tools needed for this task (read/glob, file edits, and translator Task). Do not use shell, web, search, or GitHub tools for translation work.
6. Preserve frontmatter keys, internal links, code blocks, and existing locale-specific metadata unless the English change requires an update.
7. Keep locale docs structure aligned with their corresponding English pages.
8. Do not modify English source docs in packages/web/src/content/docs/*.mdx.
9. If no locale updates are needed, make no changes.
EOF
- name: Commit and push locale docs updates
if: steps.changes.outputs.has_changes == 'true'
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "No locale docs changes to commit"
exit 0
fi
git add -A
git commit -m "docs(i18n): sync locale docs from english changes"
git pull --rebase --autostash origin "$GITHUB_REF_NAME"
git push origin HEAD:"$GITHUB_REF_NAME"
-72
View File
@@ -1,72 +0,0 @@
name: docs-update
on:
schedule:
- cron: "0 */12 * * *"
workflow_dispatch:
env:
LOOKBACK_HOURS: 4
jobs:
update-docs:
if: github.repository == 'sst/opencode'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
id-token: write
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0 # Fetch full history to access commits
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Get recent commits
id: commits
run: |
COMMITS=$(git log --since="${{ env.LOOKBACK_HOURS }} hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "")
if [ -z "$COMMITS" ]; then
echo "No commits in the last ${{ env.LOOKBACK_HOURS }} hours"
echo "has_commits=false" >> $GITHUB_OUTPUT
else
echo "has_commits=true" >> $GITHUB_OUTPUT
{
echo "list<<EOF"
echo "$COMMITS"
echo "EOF"
} >> $GITHUB_OUTPUT
fi
- name: Run opencode
if: steps.commits.outputs.has_commits == 'true'
uses: sst/opencode/github@2c14fc5586fe0b88e5c04732d2e846769cc35671 # latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
with:
model: opencode/gpt-5.2
agent: docs
prompt: |
Review the following commits from the last ${{ env.LOOKBACK_HOURS }} hours and identify any new features that may need documentation.
<recent_commits>
${{ steps.commits.outputs.list }}
</recent_commits>
Steps:
1. For each commit that looks like a new feature or significant change:
- Read the changed files to understand what was added
- Check if the feature is already documented in packages/web/src/content/docs/*
2. If you find undocumented features:
- Update the relevant documentation files in packages/web/src/content/docs/*
- Follow the existing documentation style and structure
- Make sure to document the feature clearly with examples where appropriate
3. If all new features are already documented, report that no updates are needed
4. If you are creating a new documentation file be sure to update packages/web/astro.config.mjs too.
Focus on user-facing features and API changes. Skip internal refactors, bug fixes, and test updates unless they affect user-facing behavior.
Don't feel the need to document every little thing. It is perfectly okay to make 0 changes at all.
Try to keep documentation only for large features or changes that already have a good spot to be documented.
-177
View File
@@ -1,177 +0,0 @@
name: duplicate-issues
on:
issues:
types: [opened, edited]
jobs:
check-duplicates:
if: github.event.action == 'opened'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 1
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
- name: Check duplicates and compliance
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENCODE_PERMISSION: |
{
"bash": {
"*": "deny",
"gh issue*": "allow"
},
"webfetch": "deny"
}
run: |
opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created:
Issue number: ${{ github.event.issue.number }}
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
You have TWO tasks. Perform both, then post a SINGLE comment (if needed).
---
TASK 1: CONTRIBUTING GUIDELINES COMPLIANCE CHECK
Check whether the issue follows our contributing guidelines and issue templates.
This project has three issue templates that every issue MUST use one of:
1. Bug Report - requires a Description field with real content
2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]:
3. Question - requires the Question field with real content
Additionally check:
- No AI-generated walls of text (long, AI-generated descriptions are not acceptable)
- The issue has real content, not just template placeholder text left unchanged
- Bug reports should include some context about how to reproduce
- Feature requests should explain the problem or need
- We want to push for having the user provide system description & information
Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content.
---
TASK 2: DUPLICATE CHECK
Search through existing issues (excluding #${{ github.event.issue.number }}) to find potential duplicates.
Consider:
1. Similar titles or descriptions
2. Same error messages or symptoms
3. Related functionality or components
4. Similar feature requests
Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, note the pinned keybinds issue #4997.
---
POSTING YOUR COMMENT:
Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows:
If the issue is NOT compliant, start the comment with:
<!-- issue-compliance -->
Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance
If duplicates were found, include a section about potential duplicates with links.
If the issue mentions keybinds/keyboard shortcuts, include a note about #4997.
If the issue IS compliant AND no duplicates were found AND no keybind reference, do NOT comment at all.
Use this format for the comment:
[If not compliant:]
<!-- issue-compliance -->
This issue doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md).
**What needs to be fixed:**
- [specific reasons]
Please edit this issue to address the above within **2 hours**, or it will be automatically closed.
[If duplicates found, add:]
---
This issue might be a duplicate of existing issues. Please check:
- #[issue_number]: [brief description of similarity]
[If keybind-related, add:]
For keybind-related issues, please also check our pinned keybinds documentation: #4997
[End with if not compliant:]
If you believe this was flagged incorrectly, please let a maintainer know.
Remember: post at most ONE comment combining all findings. If everything is fine, post nothing."
recheck-compliance:
if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance')
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 1
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
- name: Recheck compliance
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENCODE_PERMISSION: |
{
"bash": {
"*": "deny",
"gh issue*": "allow"
},
"webfetch": "deny"
}
run: |
opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited.
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
Re-check whether the issue now follows our contributing guidelines and issue templates.
This project has three issue templates that every issue MUST use one of:
1. Bug Report - requires a Description field with real content
2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]:
3. Question - requires the Question field with real content
Additionally check:
- No AI-generated walls of text (long, AI-generated descriptions are not acceptable)
- The issue has real content, not just template placeholder text left unchanged
- Bug reports should include some context about how to reproduce
- Feature requests should explain the problem or need
- We want to push for having the user provide system description & information
Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content.
If the issue is NOW compliant:
1. Remove the needs:compliance label: gh issue edit ${{ github.event.issue.number }} --remove-label needs:compliance
2. Find and delete the previous compliance comment (the one containing <!-- issue-compliance -->) using: gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments --jq '.[] | select(.body | contains(\"<!-- issue-compliance -->\")) | .id' then delete it with: gh api -X DELETE repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments/{id}
3. Post a short comment thanking them for updating the issue.
If the issue is STILL not compliant:
Post a comment explaining what still needs to be fixed. Keep the needs:compliance label."
-51
View File
@@ -1,51 +0,0 @@
name: generate
on:
push:
branches:
- dev
jobs:
generate:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Setup git committer
id: committer
uses: ./.github/actions/setup-git-committer
with:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
- name: Generate
run: ./script/generate.ts
- name: Commit and push
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "No changes to commit"
exit 0
fi
git add -A
git commit -m "chore: generate" --allow-empty
git push origin HEAD:${{ github.ref_name }} --no-verify
# if ! git push origin HEAD:${{ github.event.pull_request.head.ref || github.ref_name }} --no-verify; then
# echo ""
# echo "============================================"
# echo "Failed to push generated code."
# echo "Please run locally and push:"
# echo ""
# echo " ./script/generate.ts"
# echo " git add -A && git commit -m \"chore: generate\" && git push"
# echo ""
# echo "============================================"
# exit 1
# fi
-95
View File
@@ -1,95 +0,0 @@
name: nix-eval
on:
push:
branches: [dev]
pull_request:
branches: [dev]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
nix-eval:
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix
uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34
- name: Evaluate flake outputs (all systems)
run: |
set -euo pipefail
nix --version
echo "=== Flake metadata ==="
nix flake metadata
echo ""
echo "=== Flake structure ==="
nix flake show --all-systems
SYSTEMS="x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin"
PACKAGES="opencode"
# TODO: move 'desktop' to PACKAGES when #11755 is fixed
OPTIONAL_PACKAGES="desktop"
echo ""
echo "=== Evaluating packages for all systems ==="
for system in $SYSTEMS; do
echo ""
echo "--- $system ---"
for pkg in $PACKAGES; do
printf " %s: " "$pkg"
if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then
echo "✓"
else
echo "✗"
echo "::error::Evaluation failed for packages.$system.$pkg"
echo "$output"
exit 1
fi
done
done
echo ""
echo "=== Evaluating optional packages ==="
for system in $SYSTEMS; do
echo ""
echo "--- $system ---"
for pkg in $OPTIONAL_PACKAGES; do
printf " %s: " "$pkg"
if output=$(nix eval ".#packages.$system.$pkg.drvPath" --raw 2>&1); then
echo "✓"
else
echo "✗"
echo "::warning::Evaluation failed for packages.$system.$pkg"
echo "$output"
fi
done
done
echo ""
echo "=== Evaluating devShells for all systems ==="
for system in $SYSTEMS; do
printf "%s: " "$system"
if output=$(nix eval ".#devShells.$system.default.drvPath" --raw 2>&1); then
echo "✓"
else
echo "✗"
echo "::error::Evaluation failed for devShells.$system.default"
echo "$output"
exit 1
fi
done
echo ""
echo "=== All evaluations passed ==="
-152
View File
@@ -1,152 +0,0 @@
name: nix-hashes
permissions:
contents: write
on:
workflow_dispatch:
push:
branches: [dev, beta]
paths:
- "bun.lock"
- "package.json"
- "packages/*/package.json"
- "flake.lock"
- "nix/node_modules.nix"
- "nix/scripts/**"
- "patches/**"
- ".github/workflows/nix-hashes.yml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Native runners required: bun install cross-compilation flags (--os/--cpu)
# do not produce byte-identical node_modules as native installs.
compute-hash:
strategy:
fail-fast: false
matrix:
include:
- system: x86_64-linux
runner: blacksmith-4vcpu-ubuntu-2404
- system: aarch64-linux
runner: blacksmith-4vcpu-ubuntu-2404-arm
- system: x86_64-darwin
runner: macos-15-intel
- system: aarch64-darwin
runner: macos-latest
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Nix
uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34
- name: Compute node_modules hash
id: hash
env:
SYSTEM: ${{ matrix.system }}
run: |
set -euo pipefail
BUILD_LOG=$(mktemp)
trap 'rm -f "$BUILD_LOG"' EXIT
# Build with fakeHash to trigger hash mismatch and reveal correct hash
nix build ".#packages.${SYSTEM}.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true
# Extract hash from build log with portability
HASH="$(nix run --inputs-from . nixpkgs#gnugrep -- -oP 'got:\s*\Ksha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | tail -n1 || true)"
if [ -z "$HASH" ]; then
echo "::error::Failed to compute hash for ${SYSTEM}"
cat "$BUILD_LOG"
exit 1
fi
echo "$HASH" > hash.txt
echo "Computed hash for ${SYSTEM}: $HASH"
- name: Upload hash
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: hash-${{ matrix.system }}
path: hash.txt
retention-days: 1
update-hashes:
needs: compute-hash
if: github.event_name != 'pull_request'
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Setup git committer
uses: ./.github/actions/setup-git-committer
with:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
- name: Pull latest changes
run: |
git pull --rebase --autostash origin "$GITHUB_REF_NAME"
- name: Download hash artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: hashes
pattern: hash-*
- name: Update hashes.json
run: |
set -euo pipefail
HASH_FILE="nix/hashes.json"
[ -f "$HASH_FILE" ] || echo '{"nodeModules":{}}' > "$HASH_FILE"
for SYSTEM in x86_64-linux aarch64-linux x86_64-darwin aarch64-darwin; do
FILE="hashes/hash-${SYSTEM}/hash.txt"
if [ -f "$FILE" ]; then
HASH="$(tr -d '[:space:]' < "$FILE")"
echo "${SYSTEM}: ${HASH}"
jq --arg sys "$SYSTEM" --arg h "$HASH" '.nodeModules[$sys] = $h' "$HASH_FILE" > tmp.json
mv tmp.json "$HASH_FILE"
else
echo "::warning::Missing hash for ${SYSTEM}"
fi
done
cat "$HASH_FILE"
- name: Commit changes
run: |
set -euo pipefail
HASH_FILE="nix/hashes.json"
if [ -z "$(git status --short -- "$HASH_FILE")" ]; then
echo "No changes to commit"
echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY"
echo "Status: no changes" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
git add "$HASH_FILE"
git commit -m "chore: update nix node_modules hashes"
git pull --rebase --autostash origin "$GITHUB_REF_NAME"
git push origin HEAD:"$GITHUB_REF_NAME"
echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY"
echo "Status: committed $(git rev-parse --short HEAD)" >> "$GITHUB_STEP_SUMMARY"
-14
View File
@@ -1,14 +0,0 @@
name: notify-discord
on:
release:
types: [released] # fires when a draft release is published
jobs:
notify:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Send nicely-formatted embed to Discord
uses: SethCohen/github-releases-to-discord@24d166886aee4646d448c8a389ff9e1ebcab3682 # v1.20.0
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
-34
View File
@@ -1,34 +0,0 @@
name: opencode
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
opencode:
if: |
contains(github.event.comment.body, ' /oc') ||
startsWith(github.event.comment.body, '/oc') ||
contains(github.event.comment.body, ' /opencode') ||
startsWith(github.event.comment.body, '/opencode')
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
id-token: write
contents: read
pull-requests: read
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: ./.github/actions/setup-bun
- name: Run opencode
uses: anomalyco/opencode/github@2c14fc5586fe0b88e5c04732d2e846769cc35671 # latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
OPENCODE_PERMISSION: '{"bash": "deny"}'
with:
model: opencode/claude-opus-4-5
-95
View File
@@ -1,95 +0,0 @@
name: pr-management
on:
pull_request_target:
types: [opened]
jobs:
check-duplicates:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 1
- name: Check team membership
id: team-check
run: |
LOGIN="${{ github.event.pull_request.user.login }}"
if [ "$LOGIN" = "opencode-agent[bot]" ] || grep -qxF "$LOGIN" .github/TEAM_MEMBERS; then
echo "is_team=true" >> "$GITHUB_OUTPUT"
echo "Skipping: $LOGIN is a team member or bot"
else
echo "is_team=false" >> "$GITHUB_OUTPUT"
fi
- name: Setup Bun
if: steps.team-check.outputs.is_team != 'true'
uses: ./.github/actions/setup-bun
- name: Install dependencies
if: steps.team-check.outputs.is_team != 'true'
run: bun install
- name: Install opencode
if: steps.team-check.outputs.is_team != 'true'
run: curl -fsSL https://opencode.ai/install | bash
- name: Build prompt
if: steps.team-check.outputs.is_team != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
{
echo "Check for duplicate PRs related to this new PR:"
echo ""
echo "CURRENT_PR_NUMBER: $PR_NUMBER"
echo ""
echo "Title: $(gh pr view "$PR_NUMBER" --json title --jq .title)"
echo ""
echo "Description:"
gh pr view "$PR_NUMBER" --json body --jq .body
} > pr_info.txt
- name: Check for duplicate PRs
if: steps.team-check.outputs.is_team != 'true'
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
COMMENT=$(bun script/duplicate-pr.ts -f pr_info.txt "Check the attached file for PR details and search for duplicates")
if [ "$COMMENT" != "No duplicate PRs found" ]; then
gh pr comment "$PR_NUMBER" --body "_The following comment was made by an LLM, it may be inaccurate:_
$COMMENT"
fi
add-contributor-label:
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
steps:
- name: Add Contributor Label
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const isPR = !!context.payload.pull_request;
const issueNumber = isPR ? context.payload.pull_request.number : context.payload.issue.number;
const authorAssociation = isPR ? context.payload.pull_request.author_association : context.payload.issue.author_association;
if (authorAssociation === 'CONTRIBUTOR') {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: ['contributor']
});
}
-351
View File
@@ -1,351 +0,0 @@
name: pr-standards
on:
pull_request_target:
types: [opened, edited, synchronize]
jobs:
check-standards:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Check PR standards
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const pr = context.payload.pull_request;
const login = pr.user.login;
// Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC)
const cutoff = new Date('2026-02-19T00:00:00Z');
const prCreated = new Date(pr.created_at);
if (prCreated < cutoff) {
console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`);
return;
}
// Check if author is a team member or bot
if (login === 'opencode-agent[bot]') return;
const { data: file } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: '.github/TEAM_MEMBERS',
ref: 'dev'
});
const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean);
if (members.includes(login)) {
console.log(`Skipping: ${login} is a team member`);
return;
}
const title = pr.title;
async function addLabel(label) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [label]
});
}
async function removeLabel(label) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label
});
} catch (e) {
// Label wasn't present, ignore
}
}
async function comment(marker, body) {
const markerText = `<!-- pr-standards:${marker} -->`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});
const existing = comments.find(c => c.body.includes(markerText));
if (existing) return;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: markerText + '\n' + body
});
}
// Step 1: Check title format
// Matches: feat:, feat(scope):, feat (scope):, etc.
const titlePattern = /^(feat|fix|docs|chore|refactor|test)\s*(\([a-zA-Z0-9-]+\))?\s*:/;
const hasValidTitle = titlePattern.test(title);
if (!hasValidTitle) {
await addLabel('needs:title');
await comment('title', `Hey! Your PR title \`${title}\` doesn't follow conventional commit format.
Please update it to start with one of:
- \`feat:\` or \`feat(scope):\` new feature
- \`fix:\` or \`fix(scope):\` bug fix
- \`docs:\` or \`docs(scope):\` documentation changes
- \`chore:\` or \`chore(scope):\` maintenance tasks
- \`refactor:\` or \`refactor(scope):\` code refactoring
- \`test:\` or \`test(scope):\` adding or updating tests
Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`opencode\`).
See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#pr-titles) for details.`);
return;
}
await removeLabel('needs:title');
// Step 2: Check for linked issue (skip for docs/refactor/feat PRs)
const skipIssueCheck = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
if (skipIssueCheck) {
await removeLabel('needs:issue');
console.log('Skipping issue check for docs/refactor/feat PR');
return;
}
const query = `
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
closingIssuesReferences(first: 1) {
totalCount
}
}
}
}
`;
const result = await github.graphql(query, {
owner: context.repo.owner,
repo: context.repo.repo,
number: pr.number
});
const linkedIssues = result.repository.pullRequest.closingIssuesReferences.totalCount;
if (linkedIssues === 0) {
await addLabel('needs:issue');
await comment('issue', `Thanks for your contribution!
This PR doesn't have a linked issue. All PRs must reference an existing issue.
Please:
1. Open an issue describing the bug/feature (if one doesn't exist)
2. Add \`Fixes #<number>\` or \`Closes #<number>\` to this PR description
See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#issue-first-policy) for details.`);
return;
}
await removeLabel('needs:issue');
console.log('PR meets all standards');
check-compliance:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Check PR template compliance
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const pr = context.payload.pull_request;
const login = pr.user.login;
// Skip PRs older than Feb 18, 2026 at 6PM EST (Feb 19, 2026 00:00 UTC)
const cutoff = new Date('2026-02-19T00:00:00Z');
const prCreated = new Date(pr.created_at);
if (prCreated < cutoff) {
console.log(`Skipping: PR #${pr.number} was created before cutoff (${prCreated.toISOString()})`);
return;
}
// Check if author is a team member or bot
if (login === 'opencode-agent[bot]') return;
const { data: file } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: '.github/TEAM_MEMBERS',
ref: 'dev'
});
const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean);
if (members.includes(login)) {
console.log(`Skipping: ${login} is a team member`);
return;
}
const body = pr.body || '';
const title = pr.title;
const isDocsRefactorOrFeat = /^(docs|refactor|feat)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
const issues = [];
// Check: template sections exist
const hasWhatSection = /### What does this PR do\?/.test(body);
const hasTypeSection = /### Type of change/.test(body);
const hasVerifySection = /### How did you verify your code works\?/.test(body);
const hasChecklistSection = /### Checklist/.test(body);
const hasIssueSection = /### Issue for this PR/.test(body);
if (!hasWhatSection || !hasTypeSection || !hasVerifySection || !hasChecklistSection || !hasIssueSection) {
issues.push('PR description is missing required template sections. Please use the [PR template](../blob/dev/.github/pull_request_template.md).');
}
// Check: "What does this PR do?" has real content (not just placeholder text)
if (hasWhatSection) {
const whatMatch = body.match(/### What does this PR do\?\s*\n([\s\S]*?)(?=###|$)/);
const whatContent = whatMatch ? whatMatch[1].trim() : '';
const placeholder = 'Please provide a description of the issue';
const onlyPlaceholder = whatContent.includes(placeholder) && whatContent.replace(placeholder, '').replace(/[*\s]/g, '').length < 20;
if (!whatContent || onlyPlaceholder) {
issues.push('"What does this PR do?" section is empty or only contains placeholder text. Please describe your changes.');
}
}
// Check: at least one "Type of change" checkbox is checked
if (hasTypeSection) {
const typeMatch = body.match(/### Type of change\s*\n([\s\S]*?)(?=###|$)/);
const typeContent = typeMatch ? typeMatch[1] : '';
const hasCheckedBox = /- \[x\]/i.test(typeContent);
if (!hasCheckedBox) {
issues.push('No "Type of change" checkbox is checked. Please select at least one.');
}
}
// Check: issue reference (skip for docs/refactor/feat)
if (!isDocsRefactorOrFeat && hasIssueSection) {
const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/);
const issueContent = issueMatch ? issueMatch[1].trim() : '';
const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent);
if (!hasIssueRef) {
issues.push('No issue referenced. Please add `Closes #<number>` linking to the relevant issue.');
}
}
// Check: "How did you verify" has content
if (hasVerifySection) {
const verifyMatch = body.match(/### How did you verify your code works\?\s*\n([\s\S]*?)(?=###|$)/);
const verifyContent = verifyMatch ? verifyMatch[1].trim() : '';
if (!verifyContent) {
issues.push('"How did you verify your code works?" section is empty. Please explain how you tested.');
}
}
// Check: checklist boxes are checked
if (hasChecklistSection) {
const checklistMatch = body.match(/### Checklist\s*\n([\s\S]*?)(?=###|$)/);
const checklistContent = checklistMatch ? checklistMatch[1] : '';
const unchecked = (checklistContent.match(/- \[ \]/g) || []).length;
const checked = (checklistContent.match(/- \[x\]/gi) || []).length;
if (checked < 2) {
issues.push('Not all checklist items are checked. Please confirm you have tested locally and have not included unrelated changes.');
}
}
// Helper functions
async function addLabel(label) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [label]
});
}
async function removeLabel(label) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label
});
} catch (e) {}
}
const hasComplianceLabel = pr.labels.some(l => l.name === 'needs:compliance');
if (issues.length > 0) {
// Non-compliant
if (!hasComplianceLabel) {
await addLabel('needs:compliance');
}
const marker = '<!-- issue-compliance -->';
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});
const existing = comments.find(c => c.body.includes(marker));
const body_text = `${marker}
This PR doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) and [PR template](../blob/dev/.github/pull_request_template.md).
**What needs to be fixed:**
${issues.map(i => `- ${i}`).join('\n')}
Please edit this PR description to address the above within **2 hours**, or it will be automatically closed.
If you believe this was flagged incorrectly, please let a maintainer know.`;
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body_text
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: body_text
});
}
console.log(`PR #${pr.number} is non-compliant: ${issues.join(', ')}`);
} else if (hasComplianceLabel) {
// Was non-compliant, now fixed
await removeLabel('needs:compliance');
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});
const marker = '<!-- issue-compliance -->';
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id
});
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: 'Thanks for updating your PR! It now meets our contributing guidelines. :+1:'
});
console.log(`PR #${pr.number} is now compliant, label removed`);
} else {
console.log(`PR #${pr.number} is compliant`);
}
@@ -1,30 +0,0 @@
name: publish-github-action
on:
workflow_dispatch:
push:
tags:
- "github-v*.*.*"
- "!github-v1"
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
jobs:
publish:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with:
fetch-depth: 0
- run: git fetch --force --tags
- name: Publish
run: |
git config --global user.email "opencode@sst.dev"
git config --global user.name "opencode"
./script/publish
working-directory: ./github
-37
View File
@@ -1,37 +0,0 @@
name: publish-vscode
on:
workflow_dispatch:
push:
tags:
- "vscode-v*.*.*"
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
jobs:
publish:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with:
fetch-depth: 0
- uses: ./.github/actions/setup-bun
- run: git fetch --force --tags
- run: bun install -g @vscode/vsce
- name: Install extension dependencies
run: bun install
working-directory: ./sdks/vscode
- name: Publish
run: |
./script/publish
working-directory: ./sdks/vscode
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
OPENVSX_TOKEN: ${{ secrets.OPENVSX_TOKEN }}
-491
View File
@@ -1,491 +0,0 @@
name: publish
run-name: "${{ format('release {0}', inputs.bump) }}"
on:
push:
branches:
- ci
- dev
- beta
- fix/npm-native-binary-install
- snapshot-*
workflow_dispatch:
inputs:
bump:
description: "Bump major, minor, or patch"
required: false
type: choice
options:
- major
- minor
- patch
version:
description: "Override version (optional)"
required: false
type: string
concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.version || inputs.bump }}
permissions:
id-token: write
contents: write
packages: write
jobs:
version:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.repository == 'anomalyco/opencode'
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with:
fetch-depth: 0
- uses: ./.github/actions/setup-bun
- name: Setup git committer
id: committer
uses: ./.github/actions/setup-git-committer
with:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
- name: Install OpenCode
if: inputs.bump || inputs.version
run: bun i -g opencode-ai
- id: version
run: |
./script/version.ts
env:
GH_TOKEN: ${{ steps.committer.outputs.token }}
OPENCODE_BUMP: ${{ inputs.bump }}
OPENCODE_VERSION: ${{ inputs.version }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GH_REPO: ${{ (github.ref_name == 'beta' && 'anomalyco/opencode-beta') || github.repository }}
outputs:
version: ${{ steps.version.outputs.version }}
release: ${{ steps.version.outputs.release }}
tag: ${{ steps.version.outputs.tag }}
repo: ${{ steps.version.outputs.repo }}
build-cli:
needs: version
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.repository == 'anomalyco/opencode'
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
with:
fetch-tags: true
- uses: ./.github/actions/setup-bun
- name: Setup git committer
id: committer
uses: ./.github/actions/setup-git-committer
with:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
- name: Build
id: build
run: |
./packages/opencode/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
env:
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
GH_REPO: ${{ needs.version.outputs.repo }}
GH_TOKEN: ${{ steps.committer.outputs.token }}
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: opencode-cli
path: |
packages/opencode/dist/opencode-darwin*
packages/opencode/dist/opencode-linux*
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: opencode-cli-windows
path: packages/opencode/dist/opencode-windows*
outputs:
version: ${{ needs.version.outputs.version }}
sign-cli-windows:
needs:
- build-cli
- version
runs-on: blacksmith-4vcpu-windows-2025
if: github.repository == 'anomalyco/opencode'
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: opencode-cli-windows
path: packages/opencode/dist
- name: Setup git committer
id: committer
uses: ./.github/actions/setup-git-committer
with:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
- name: Azure login
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
client-id: ${{ env.AZURE_CLIENT_ID }}
tenant-id: ${{ env.AZURE_TENANT_ID }}
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
- uses: azure/artifact-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1.2.0
with:
endpoint: ${{ env.AZURE_TRUSTED_SIGNING_ENDPOINT }}
signing-account-name: ${{ env.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ env.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
files: |
${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64\bin\opencode.exe
${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64\bin\opencode.exe
${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline\bin\opencode.exe
exclude-environment-credential: true
exclude-workload-identity-credential: true
exclude-managed-identity-credential: true
exclude-shared-token-cache-credential: true
exclude-visual-studio-credential: true
exclude-visual-studio-code-credential: true
exclude-azure-cli-credential: false
exclude-azure-powershell-credential: true
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true
- name: Verify Windows CLI signatures
shell: pwsh
run: |
$files = @(
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64\bin\opencode.exe",
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64\bin\opencode.exe",
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline\bin\opencode.exe"
)
foreach ($file in $files) {
$sig = Get-AuthenticodeSignature $file
if ($sig.Status -ne "Valid") {
throw "Invalid signature for ${file}: $($sig.Status)"
}
}
- name: Repack Windows CLI archives
working-directory: packages/opencode/dist
shell: pwsh
run: |
Compress-Archive -Path "opencode-windows-arm64\bin\*" -DestinationPath "opencode-windows-arm64.zip" -Force
Compress-Archive -Path "opencode-windows-x64\bin\*" -DestinationPath "opencode-windows-x64.zip" -Force
Compress-Archive -Path "opencode-windows-x64-baseline\bin\*" -DestinationPath "opencode-windows-x64-baseline.zip" -Force
- name: Upload signed Windows CLI release assets
if: needs.version.outputs.release != ''
shell: pwsh
env:
GH_TOKEN: ${{ steps.committer.outputs.token }}
run: |
gh release upload "v${{ needs.version.outputs.version }}" `
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64.zip" `
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64.zip" `
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline.zip" `
--clobber `
--repo "${{ needs.version.outputs.repo }}"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: opencode-cli-signed-windows
path: |
packages/opencode/dist/opencode-windows-arm64
packages/opencode/dist/opencode-windows-x64
packages/opencode/dist/opencode-windows-x64-baseline
build-electron:
needs:
- build-cli
- version
if: github.repository == 'anomalyco/opencode'
continue-on-error: false
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE }}
AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }}
strategy:
fail-fast: false
matrix:
settings:
- host: macos-26-intel
target: x86_64-apple-darwin
platform_flag: --mac --x64
bun_install_flags: --os=darwin --cpu=x64
- host: macos-26
target: aarch64-apple-darwin
platform_flag: --mac --arm64
bun_install_flags: --os=darwin --cpu=arm64
# github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain
- host: "windows-2025"
target: aarch64-pc-windows-msvc
platform_flag: --win --arm64
- host: "blacksmith-4vcpu-windows-2025"
target: x86_64-pc-windows-msvc
platform_flag: --win
- host: "blacksmith-4vcpu-ubuntu-2404"
target: x86_64-unknown-linux-gnu
platform_flag: --linux
- host: "blacksmith-4vcpu-ubuntu-2404-arm"
target: aarch64-unknown-linux-gnu
platform_flag: --linux --arm64
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0
if: runner.os == 'macOS'
with:
keychain: build
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
- name: Setup Apple API Key
if: runner.os == 'macOS'
run: echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
- uses: ./.github/actions/setup-bun
with:
install-flags: ${{ matrix.settings.bun_install_flags }}
- name: Azure login
if: runner.os == 'Windows'
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
client-id: ${{ env.AZURE_CLIENT_ID }}
tenant-id: ${{ env.AZURE_TENANT_ID }}
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24"
- name: Cache apt packages
if: contains(matrix.settings.host, 'ubuntu')
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ~/apt-cache
key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-${{ hashFiles('.github/workflows/publish.yml') }}
restore-keys: |
${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-
- name: Install dependencies (ubuntu only)
if: contains(matrix.settings.host, 'ubuntu')
run: |
mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache
sudo apt-get update
sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" rpm
sudo chmod -R a+rw ~/apt-cache
- name: Setup git committer
id: committer
uses: ./.github/actions/setup-git-committer
with:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
- name: Prepare
run: bun ./scripts/prepare.ts
working-directory: packages/desktop
env:
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
OPENCODE_CLI_ARTIFACT: ${{ (runner.os == 'Windows' && 'opencode-cli-windows') || 'opencode-cli' }}
RUST_TARGET: ${{ matrix.settings.target }}
GH_TOKEN: ${{ github.token }}
GITHUB_RUN_ID: ${{ github.run_id }}
- name: Build
run: bun run build
working-directory: packages/desktop
env:
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
VITE_SENTRY_ENVIRONMENT: ${{ (github.ref_name == 'beta' && 'beta') || 'production' }}
VITE_SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
- name: Package and publish
if: needs.version.outputs.release
run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish always --config electron-builder.config.ts
working-directory: packages/desktop
timeout-minutes: 60
env:
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
GH_TOKEN: ${{ steps.committer.outputs.token }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
- name: Package (no publish)
if: ${{ !needs.version.outputs.release }}
run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts
working-directory: packages/desktop
timeout-minutes: 60
env:
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
- name: Create and upload macOS .app.tar.gz
if: runner.os == 'macOS' && needs.version.outputs.release
working-directory: packages/desktop/dist
env:
GH_TOKEN: ${{ steps.committer.outputs.token }}
run: |
if [[ "${{ matrix.settings.target }}" == "x86_64-apple-darwin" ]]; then
APP_DIR="mac"
OUT_NAME="opencode-desktop-mac-x64.app.tar.gz"
elif [[ "${{ matrix.settings.target }}" == "aarch64-apple-darwin" ]]; then
APP_DIR="mac-arm64"
OUT_NAME="opencode-desktop-mac-arm64.app.tar.gz"
else
echo "Unknown macOS target: ${{ matrix.settings.target }}"
exit 1
fi
APP_PATH=$(find "$APP_DIR" -maxdepth 1 -name "*.app" -type d | head -1)
if [ -z "$APP_PATH" ]; then
echo "No .app bundle found in $APP_DIR"
exit 1
fi
tar -czf "$OUT_NAME" -C "$(dirname "$APP_PATH")" "$(basename "$APP_PATH")"
gh release upload "v${{ needs.version.outputs.version }}" "$OUT_NAME" --clobber --repo "${{ needs.version.outputs.repo }}"
- name: Verify signed Windows Electron artifacts
if: runner.os == 'Windows'
shell: pwsh
run: |
$files = @()
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*.exe" | Select-Object -ExpandProperty FullName
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*unpacked\*.exe" | Select-Object -ExpandProperty FullName
$files += Get-ChildItem "${{ github.workspace }}\packages\desktop\dist\*unpacked\resources\opencode-cli.exe" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
foreach ($file in $files | Select-Object -Unique) {
$sig = Get-AuthenticodeSignature $file
if ($sig.Status -ne "Valid") {
throw "Invalid signature for ${file}: $($sig.Status)"
}
}
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: opencode-desktop-${{ matrix.settings.target }}
path: packages/desktop/dist/*
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: needs.version.outputs.release
with:
name: latest-yml-${{ matrix.settings.target }}
path: packages/desktop/dist/latest*.yml
publish:
needs:
- version
- build-cli
- sign-cli-windows
- build-electron
if: always() && !failure() && !cancelled()
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ./.github/actions/setup-bun
- name: Login to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: opencode-cli
path: packages/opencode/dist
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: opencode-cli-windows
path: packages/opencode/dist
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: opencode-cli-signed-windows
path: packages/opencode/dist
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
if: needs.version.outputs.release
with:
pattern: latest-yml-*
path: /tmp/latest-yml
- name: Setup git committer
id: committer
uses: ./.github/actions/setup-git-committer
with:
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
- name: Cache apt packages (AUR)
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: /var/cache/apt/archives
key: ${{ runner.os }}-apt-aur-${{ hashFiles('.github/workflows/publish.yml') }}
restore-keys: |
${{ runner.os }}-apt-aur-
- name: Setup SSH for AUR
run: |
sudo apt-get update
sudo apt-get install -y pacman-package-manager
mkdir -p ~/.ssh
echo "${{ secrets.AUR_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
git config --global user.email "opencode@sst.dev"
git config --global user.name "opencode"
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
- run: ./script/publish.ts
env:
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
AUR_KEY: ${{ secrets.AUR_KEY }}
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
GH_REPO: ${{ needs.version.outputs.repo }}
NPM_CONFIG_PROVENANCE: false
LATEST_YML_DIR: /tmp/latest-yml
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
@@ -1,29 +0,0 @@
name: release-github-action
on:
push:
branches:
- dev
paths:
- "github/**"
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
jobs:
release:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
- run: git fetch --force --tags
- name: Release
run: |
git config --global user.email "opencode@sst.dev"
git config --global user.name "opencode"
./github/script/release
-83
View File
@@ -1,83 +0,0 @@
name: review
on:
issue_comment:
types: [created]
jobs:
check-guidelines:
if: |
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/review') &&
contains(fromJson('["OWNER","MEMBER"]'), github.event.comment.author_association)
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
pull-requests: write
steps:
- name: Get PR number
id: pr-number
run: |
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
else
echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
fi
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 1
- uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
- name: Get PR details
id: pr-details
run: |
gh api /repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }} > pr_data.json
echo "title=$(jq -r .title pr_data.json)" >> $GITHUB_OUTPUT
echo "sha=$(jq -r .head.sha pr_data.json)" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check PR guidelines compliance
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENCODE_PERMISSION: '{ "bash": { "*": "deny", "gh*": "allow", "gh pr review*": "deny" } }'
PR_TITLE: ${{ steps.pr-details.outputs.title }}
run: |
PR_BODY=$(jq -r .body pr_data.json)
opencode run -m opencode/gpt-5.5 --variant medium "A new pull request has been created: '${PR_TITLE}'
<pr-number>
${{ steps.pr-number.outputs.number }}
</pr-number>
<pr-description>
$PR_BODY
</pr-description>
Please check all the code changes in this pull request against the style guide, also look for any bugs if they exist. Diffs are important but make sure you read the entire file to get proper context. Make it clear the suggestions are merely suggestions and the human can decide what to do
When critiquing code against the style guide, be sure that the code is ACTUALLY in violation, don't complain about else statements if they already use early returns there. You may complain about excessive nesting though, regardless of else statement usage.
When critiquing code style don't be a zealot, we don't like "let" statements but sometimes they are the simplest option, if someone does a bunch of nesting with let, they should consider using iife (see packages/opencode/src/util.iife.ts)
Use the gh cli to create comments on the files for the violations. Try to leave the comment on the exact line number. If you have a suggested fix include it in a suggestion code block.
If you are writing suggested fixes, BE SURE THAT the change you are recommending is actually valid typescript, often I have seen missing closing "}" or other syntax errors.
Generally, write a comment instead of writing suggested change if you can help it.
Command MUST be like this.
\`\`\`
gh api \
--method POST \
-H \"Accept: application/vnd.github+json\" \
-H \"X-GitHub-Api-Version: 2022-11-28\" \
/repos/${{ github.repository }}/pulls/${{ steps.pr-number.outputs.number }}/comments \
-f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT'
\`\`\`
Only create comments for actual violations. If the code follows all guidelines, comment on the issue using gh cli: 'lgtm' AND NOTHING ELSE!!!!."
-35
View File
@@ -1,35 +0,0 @@
name: stats
on:
schedule:
- cron: "0 12 * * *" # Run daily at 12:00 UTC
workflow_dispatch: # Allow manual trigger
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
stats:
if: github.repository == 'anomalyco/opencode'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Run stats script
run: bun script/stats.ts
- name: Commit stats
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add STATS.md
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
git push
env:
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
-38
View File
@@ -1,38 +0,0 @@
name: storybook
on:
push:
branches: [dev]
paths:
- ".github/workflows/storybook.yml"
- "package.json"
- "bun.lock"
- "packages/storybook/**"
- "packages/ui/**"
pull_request:
branches: [dev]
paths:
- ".github/workflows/storybook.yml"
- "package.json"
- "bun.lock"
- "packages/storybook/**"
- "packages/ui/**"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: storybook build
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Build Storybook
run: bun --cwd packages/storybook build
-35
View File
@@ -1,35 +0,0 @@
name: "sync-zed-extension"
on:
workflow_dispatch:
release:
types: [published]
jobs:
zed:
name: Release Zed Extension
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
- uses: ./.github/actions/setup-bun
- name: Get version tag
id: get_tag
run: |
if [ "${{ github.event_name }}" = "release" ]; then
TAG="${{ github.event.release.tag_name }}"
else
TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1)
fi
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "Using tag: ${TAG}"
- name: Sync Zed extension
run: |
./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }}
env:
ZED_EXTENSIONS_PAT: ${{ secrets.ZED_EXTENSIONS_PAT }}
ZED_PR_PAT: ${{ secrets.ZED_PR_PAT }}
-166
View File
@@ -1,166 +0,0 @@
name: test
on:
push:
branches:
- dev
pull_request:
workflow_dispatch:
concurrency:
# Keep every run on dev so cancelled checks do not pollute the default branch
# commit history. PRs and other branches still share a group and cancel stale runs.
group: ${{ case(github.ref == 'refs/heads/dev', format('{0}-{1}', github.workflow, github.run_id), format('{0}-{1}', github.workflow, github.event.pull_request.number || github.ref)) }}
cancel-in-progress: true
permissions:
contents: read
checks: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
unit:
name: unit (${{ matrix.settings.name }})
strategy:
fail-fast: false
matrix:
settings:
- name: linux
host: blacksmith-4vcpu-ubuntu-2404
- name: windows
host: blacksmith-4vcpu-windows-2025
runs-on: ${{ matrix.settings.host }}
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24"
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Configure git identity
run: |
git config --global user.email "bot@opencode.ai"
git config --global user.name "opencode"
- name: Cache Turbo
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: node_modules/.cache/turbo
key: turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-${{ hashFiles('turbo.json', '**/package.json') }}-
turbo-${{ runner.os }}-
- name: Run unit tests
run: bun turbo test:ci
env:
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
- name: Run HttpApi exerciser gates
if: runner.os == 'Linux'
working-directory: packages/opencode
run: bun run test:httpapi
- name: Publish unit reports
if: always()
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v6.4.0
with:
report_paths: packages/*/.artifacts/unit/junit.xml
check_name: "unit results (${{ matrix.settings.name }})"
detailed_summary: true
include_time_in_summary: true
fail_on_failure: false
- name: Upload unit artifacts
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: unit-${{ matrix.settings.name }}-${{ github.run_attempt }}
include-hidden-files: true
if-no-files-found: ignore
retention-days: 7
path: packages/*/.artifacts/unit/junit.xml
e2e:
name: e2e (${{ matrix.settings.name }})
strategy:
fail-fast: false
matrix:
settings:
- name: linux
host: blacksmith-4vcpu-ubuntu-2404
- name: windows
host: blacksmith-4vcpu-windows-2025
runs-on: ${{ matrix.settings.host }}
env:
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24"
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Read Playwright version
id: playwright-version
run: |
version=$(node -e 'console.log(require("./package.json").workspaces.catalog["@playwright/test"])')
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ github.workspace }}/.playwright-browsers
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright-version.outputs.version }}-chromium
- name: Install Playwright system dependencies
if: runner.os == 'Linux'
working-directory: packages/app
run: bunx playwright install-deps chromium
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
working-directory: packages/app
run: bunx playwright install chromium
- name: Run app e2e tests
run: bun --cwd packages/app test:e2e:local
env:
CI: true
PLAYWRIGHT_JUNIT_OUTPUT: e2e/junit-${{ matrix.settings.name }}.xml
timeout-minutes: 30
- name: Upload Playwright artifacts
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: playwright-${{ matrix.settings.name }}-${{ github.run_attempt }}
if-no-files-found: ignore
retention-days: 7
path: |
packages/app/e2e/junit-*.xml
packages/app/e2e/test-results
packages/app/e2e/playwright-report
-37
View File
@@ -1,37 +0,0 @@
name: triage
on:
issues:
types: [opened]
jobs:
triage:
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 1
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Install opencode
run: curl -fsSL https://opencode.ai/install | bash
- name: Triage issue
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
run: |
opencode run --agent triage "The following issue was just opened, triage it:
Title: $ISSUE_TITLE
$ISSUE_BODY"
-21
View File
@@ -1,21 +0,0 @@
name: typecheck
on:
push:
branches: [dev]
pull_request:
branches: [dev]
workflow_dispatch:
jobs:
typecheck:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Run typecheck
run: bun typecheck
+10 -31
View File
@@ -1,32 +1,11 @@
.DS_Store
node_modules
.worktrees
.sst
.env
.env.local
.idea
.vscode
.codex
*~
playground
tmp
dist
ts-dist
.turbo
**/.serena
.serena/
/result
refs
Session.vim
/opencode.json
a.out
target
.scripts
.direnv/
# Compiled binaries
dist/neuron
dist/neuron.backup-*
dist/*.backup-*
# Local dev files
opencode-dev
UPCOMING_CHANGELOG.md
logs/
*.bun-build
tsconfig.tsbuildinfo
# Build artifacts
*.o
*.a
# macOS
.DS_Store
-5
View File
@@ -1,5 +0,0 @@
# Fake secret-looking strings used by HTTP recorder redaction tests.
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:69
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:92
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:generic-api-key:146
afa57acfda894e0ebf3c637dd710310b705c0a2f:packages/http-recorder/test/record-replay.test.ts:gcp-api-key:71
-20
View File
@@ -1,20 +0,0 @@
#!/bin/sh
set -e
# Check if bun version matches package.json
# keep in sync with packages/script/src/index.ts semver qualifier
bun -e '
import { semver } from "bun";
const pkg = await Bun.file("package.json").json();
const expectedBunVersion = pkg.packageManager?.split("@")[1];
if (!expectedBunVersion) {
throw new Error("packageManager field not found in root package.json");
}
const expectedBunVersionRange = `^${expectedBunVersion}`;
if (!semver.satisfies(process.versions.bun, expectedBunVersionRange)) {
throw new Error(`This script requires bun@${expectedBunVersionRange}, but you are using bun@${process.versions.bun}`);
}
if (process.versions.bun !== expectedBunVersion) {
console.warn(`Warning: Bun version ${process.versions.bun} differs from expected ${expectedBunVersion}`);
}
'
bun typecheck
-7
View File
@@ -1,7 +0,0 @@
node_modules
plans
package.json
bun.lock
.gitignore
package-lock.json
references/
-26
View File
@@ -1,26 +0,0 @@
---
mode: primary
hidden: true
model: opencode/claude-haiku-4-5
color: "#E67E22"
tools:
"*": false
"github-pr-search": true
---
You are a duplicate PR detection agent. When a PR is opened, your job is to search for potentially duplicate or related open PRs.
Use the github-pr-search tool to search for PRs that might be addressing the same issue or feature.
IMPORTANT: The input will contain a line `CURRENT_PR_NUMBER: NNNN`. This is the current PR number, you should not mark that the current PR as a duplicate of itself.
Search using keywords from the PR title and description. Try multiple searches with different relevant terms.
If you find potential duplicates:
- List them with their titles and URLs
- Briefly explain why they might be related
If no duplicates are found, say so clearly. BUT ONLY SAY "No duplicate PRs found" (don't say anything else if no dups)
Keep your response concise and actionable.
-43
View File
@@ -1,43 +0,0 @@
---
mode: primary
hidden: true
model: opencode/gpt-5.4-nano
color: "#44BA81"
tools:
"*": false
"github-triage": true
---
You are a triage agent responsible for triaging github issues.
Use your github-triage tool to triage issues.
This file is the source of truth for ownership/routing rules.
Assign issues by choosing the team with the strongest overlap. The github-triage tool will assign a random member from that team.
Do not add labels to issues. Only assign an owner.
When calling github-triage, pass one of these team values: tui, desktop_web, core, inference, windows.
## Teams
### TUI
Terminal UI issues, including rendering, keybindings, scrolling, terminal compatibility, SSH behavior, crashes in the TUI, and low-level TUI performance.
### Desktop / Web
Desktop application and browser-based app issues, including `opencode web`, desktop-specific UI behavior, packaging, and web view problems.
### Core
Core opencode server and harness issues, including sqlite, snapshots, memory, API behavior, agent context construction, tool execution, provider integrations, model behavior, documentation, and larger architectural features.
### Inference
OpenCode Zen, OpenCode Go, and billing issues.
### Windows
Windows-specific issues, including native Windows behavior, WSL interactions, path handling, shell compatibility, and installation or runtime problems that only happen on Windows.
-24
View File
@@ -1,24 +0,0 @@
---
description: "Bump AI sdk dependencies minor / patch versions only"
---
Please read @package.json and @packages/opencode/package.json.
Your job is to look into AI SDK dependencies, figure out if they have versions that can be upgraded (minor or patch versions ONLY no major ignore major changes).
I want a report of every dependency and the version that can be upgraded to.
What would be even better is if you can give me brief summary of the changes for each dep and a link to the changelog for each dependency, or at least some reference info so I can see what bugs were fixed or new features were added.
Consider using subagents for each dep to save your context window.
Here is a short list of some deps (please be comprehensive tho):
- "ai"
- "@ai-sdk/openai"
- "@ai-sdk/anthropic"
- "@openrouter/ai-sdk-provider"
- etc, etc
DO NOT upgrade the dependencies yet, just make a list of all dependencies and their versions that can be upgraded to minor or patch versions only.
Write up your findings to ai-sdk-updates.md
-49
View File
@@ -1,49 +0,0 @@
---
model: opencode/gpt-5.4
---
Create `UPCOMING_CHANGELOG.md` from the structured changelog input below.
If `UPCOMING_CHANGELOG.md` already exists, ignore its current contents completely.
Do not preserve, merge, or reuse text from the existing file.
The input already contains the exact commit range since the last non-draft release.
The commits are already filtered to the release-relevant packages and grouped into
the release sections. Do not fetch GitHub releases, PRs, or build your own commit list.
The input may also include a `## Community Contributors Input` section.
Before writing any entry you keep, inspect the real diff with
`git show --stat --format='' <hash>` or `git show --format='' <hash>` so you can
understand the actual code changes and not just the commit message (they may be misleading).
Do not use `git log` or author metadata when deciding attribution.
Rules:
- Write the final file with release sections in this order:
`## Core`, `## TUI`, `## Desktop`, `## SDK`, `## Extensions`
- Only include sections that have at least one notable entry
- Within each release section, keep bug fixes grouped under `### Bugfixes`
- Keep other notable entries under `### Improvements` when a section has bug fixes too
- Omit empty subsections
- Keep one bullet per commit you keep
- Skip commits that are entirely internal, CI, tests, refactors, or otherwise not user-facing
- Start each bullet with a capital letter
- Prefer what changed for users over what code changed internally
- Do not copy raw commit prefixes like `fix:` or `feat:` or trailing PR numbers like `(#123)`
- Community attribution is deterministic: only preserve an existing `(@username)` suffix from the changelog input
- If an input bullet has no `(@username)` suffix, do not add one
- Never add a new `(@username)` suffix from `git show`, commit authors, names, or email addresses
- If no notable entries remain and there is no contributor block, write exactly `No notable changes.`
- If no notable entries remain but there is a contributor block, omit all release sections and return only the contributor block
- If the input contains `## Community Contributors Input`, append the block below that heading to the end of the final file verbatim
- Do not add, remove, rewrite, or reorder contributor names or commit titles in that block
- Do not derive the thank-you section from the main summary bullets
- Do not include the heading `## Community Contributors Input` in the final file
- Focus on writing the least words to get your point across - users will skim read the changelog, so we should be precise
**Importantly, the changelog is for users (who are at least slightly technical), they may use the TUI, Desktop, SDK, Plugins and so forth. Be thorough in understanding flow on effects may not be immediately apparent. e.g. a package upgrade looks internal but may patch a bug. Or a refactor may also stabilise some race condition that fixes bugs for users. The PR title/body + commit message will give you the authors context, usually containing the outcome not just technical detail**
<changelog_input>
!`bun script/raw-changelog.ts $ARGUMENTS`
</changelog_input>
-37
View File
@@ -1,37 +0,0 @@
---
description: git commit and push
model: opencode/kimi-k2.5
subtask: true
---
commit and push
make sure it includes a prefix like
docs:
tui:
core:
ci:
ignore:
wip:
For anything in the packages/web use the docs: prefix.
prefer to explain WHY something was done from an end user perspective instead of
WHAT was done.
do not do generic messages like "improved agent experience" be very specific
about what user facing changes were made
if there are conflicts DO NOT FIX THEM. notify me and I will fix them
## GIT DIFF
!`git diff`
## GIT DIFF --cached
!`git diff --cached`
## GIT STATUS --short
!`git status --short`
-23
View File
@@ -1,23 +0,0 @@
---
description: "find issue(s) on github"
model: opencode/claude-haiku-4-5
---
Search through existing issues in anomalyco/opencode using the gh cli to find issues matching this query:
$ARGUMENTS
Consider:
1. Similar titles or descriptions
2. Same error messages or symptoms
3. Related functionality or components
4. Similar feature requests
Please list any matching issues with:
- Issue number and title
- Brief explanation of why it matches the query
- Link to the issue
If no clear matches are found, say so.
-42
View File
@@ -1,42 +0,0 @@
---
description: Extract non-obvious learnings from session to AGENTS.md files to build codebase understanding
---
Analyze this session and extract non-obvious learnings to add to AGENTS.md files.
AGENTS.md files can exist at any directory level, not just the project root. When an agent reads a file, any AGENTS.md in parent directories are automatically loaded into the context of the tool read. Place learnings as close to the relevant code as possible:
- Project-wide learnings → root AGENTS.md
- Package/module-specific → packages/foo/AGENTS.md
- Feature-specific → src/auth/AGENTS.md
What counts as a learning (non-obvious discoveries only):
- Hidden relationships between files or modules
- Execution paths that differ from how code appears
- Non-obvious configuration, env vars, or flags
- Debugging breakthroughs when error messages were misleading
- API/tool quirks and workarounds
- Build/test commands not in README
- Architectural decisions and constraints
- Files that must change together
What NOT to include:
- Obvious facts from documentation
- Standard language/framework behavior
- Things already in an AGENTS.md
- Verbose explanations
- Session-specific details
Process:
1. Review session for discoveries, errors that took multiple attempts, unexpected connections
2. Determine scope - what directory does each learning apply to?
3. Read existing AGENTS.md files at relevant levels
4. Create or update AGENTS.md at the appropriate level
5. Keep entries to 1-3 lines per insight
After updating, summarize which AGENTS.md files were created/updated and how many learnings per file.
$ARGUMENTS
-15
View File
@@ -1,15 +0,0 @@
---
description: Remove AI code slop
---
Check the diff against dev, and remove all AI generated slop introduced in this branch.
This includes:
- Extra comments that a human wouldn't add or is inconsistent with the rest of the file
- Extra defensive checks or try/catch blocks that are abnormal for that area of the codebase (especially if called by trusted / validated codepaths)
- Casts to any to get around type issues
- Any other style that is inconsistent with the file
- Unnecessary emoji usage
Report at the end with only a 1-3 sentence summary of what you changed
-5
View File
@@ -1,5 +0,0 @@
---
description: spellcheck all markdown file changes
---
Look at all the unstaged changes to markdown (.md, .mdx) files, pull out the lines that have changed, and check for spelling and grammar errors.
-14
View File
@@ -1,14 +0,0 @@
---
description: translate English to other languages
model: opencode/claude-opus-4-7
---
run git diff and translate changed english doc and UI copy files to other international languages. Translate all languages in parallel to save time.
Requirements:
- Preserve meaning, intent, tone, and formatting (including Markdown/MDX structure).
- Preserve all technical terms and artifacts exactly: product/company names, API names, identifiers, code, commands/flags, file paths, URLs, versions, error messages, config keys/values, and anything inside inline code or code blocks.
- Also preserve every term listed in the Do-Not-Translate glossary below.
- Also apply locale-specific guidance from `.opencode/glossary/<locale>.md` when available (for example, `zh-cn.md`).
- Do not modify fenced code blocks.
-4
View File
@@ -1,4 +0,0 @@
declare module "*.txt" {
const content: string
export default content
}
-63
View File
@@ -1,63 +0,0 @@
# Locale Glossaries
Use this folder for locale-specific translation guidance that supplements `.opencode/agent/translator.md`.
The global glossary in `translator.md` remains the source of truth for shared do-not-translate terms (commands, code, paths, product names, etc.). These locale files capture community learnings about phrasing and terminology preferences.
## File Naming
- One file per locale
- Use lowercase locale slugs that match docs locales when possible (for example, `zh-cn.md`, `zh-tw.md`)
- If only language-level guidance exists, use the language code (for example, `fr.md`)
- Some repo locale slugs may be aliases/non-BCP47 for consistency (for example, `br` for Brazilian Portuguese / `pt-BR`)
## What To Put In A Locale File
- **Sources**: PRs/issues/discussions that motivated the guidance
- **Do Not Translate (Locale Additions)**: locale-specific terms or casing decisions
- **Preferred Terms**: recurring UI/docs words with preferred translations
- **Guidance**: tone, style, and consistency notes
- **Avoid** (optional): common literal translations or wording we should avoid
- If the repo uses a locale alias slug, document the alias in **Guidance** (for example, prose may mention `pt-BR` while config/examples use `br`)
Prefer guidance that is:
- Repeated across multiple docs/screens
- Easy to apply consistently
- Backed by a community contribution or review discussion
## Template
```md
# <locale> Glossary
## Sources
- PR #12345: https://github.com/anomalyco/opencode/pull/12345
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing)
## Preferred Terms
| English | Preferred | Notes |
| ------- | --------- | --------- |
| prompt | ... | preferred |
| session | ... | preferred |
## Guidance
- Prefer natural phrasing over literal translation
## Avoid
- Avoid ... when ...
```
## Contribution Notes
- Mark entries as preferred when they may evolve
- Keep examples short
- Add or update the `Sources` section whenever you add a new rule
- Prefer PR-backed guidance over invented term mappings; start with general guidance if no term-level corrections exist yet
-28
View File
@@ -1,28 +0,0 @@
# ar Glossary
## Sources
- PR #9947: https://github.com/anomalyco/opencode/pull/9947
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
## Guidance
- Prefer natural Arabic phrasing over literal translation
- Keep tone clear and direct in UI labels and docs prose
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
- For RTL text, treat code, commands, and paths as LTR artifacts and keep their character order unchanged
## Avoid
- Avoid translating product and protocol names that are fixed identifiers
- Avoid mixing multiple Arabic terms for the same recurring UI action once a preferred term is established
-34
View File
@@ -1,34 +0,0 @@
# br Glossary
## Sources
- PR #10086: https://github.com/anomalyco/opencode/pull/10086
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Locale code `br` in repo config, code, and paths (repo alias for Brazilian Portuguese)
## Preferred Terms
These are PR-backed locale naming preferences and may evolve.
| English / Context | Preferred | Notes |
| ---------------------------------------- | ------------------------------ | ------------------------------------------------------------- |
| Brazilian Portuguese (prose locale name) | `pt-BR` | Use standard locale naming in prose when helpful |
| Repo locale slug (code/config) | `br` | PR #10086 uses `br` for consistency/simplicity |
| Browser locale detection | `pt`, `pt-br`, `pt-BR` -> `br` | Preserve this mapping in docs/examples about locale detection |
## Guidance
- This file covers Brazilian Portuguese (`pt-BR`), but the repo locale code is `br`
- Use natural Brazilian Portuguese phrasing over literal translation
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
- Keep repo locale identifiers as implemented in code/config (`br`) even when prose mentions `pt-BR`
## Avoid
- Avoid changing repo locale code references from `br` to `pt-br` in code snippets, paths, or config examples
- Avoid mixing Portuguese variants when a Brazilian Portuguese form is established
-33
View File
@@ -1,33 +0,0 @@
# bs Glossary
## Sources
- PR #12283: https://github.com/anomalyco/opencode/pull/12283
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
These are PR-backed locale naming preferences and may evolve.
| English / Context | Preferred | Notes |
| ---------------------------------- | ---------- | ------------------------------------------------- |
| Bosnian language label (UI) | `Bosanski` | PR #12283 tested switching language to `Bosanski` |
| Repo locale slug (code/config) | `bs` | Preserve in code, config, paths, and examples |
| Browser locale detection (Bosnian) | `bs` | PR #12283 added `bs` locale auto-detection |
## Guidance
- Use natural Bosnian phrasing over literal translation
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
- Keep repo locale references as `bs` in code/config, and use `Bosanski` for the user-facing language name when applicable
## Avoid
- Avoid changing repo locale references from `bs` to another slug in code snippets or config examples
- Avoid translating product and protocol names that are fixed identifiers
-27
View File
@@ -1,27 +0,0 @@
# da Glossary
## Sources
- PR #9821: https://github.com/anomalyco/opencode/pull/9821
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
## Guidance
- Prefer natural Danish phrasing over literal translation
- Keep tone clear and direct in UI labels and docs prose
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
## Avoid
- Avoid translating product and protocol names that are fixed identifiers
- Avoid mixing multiple Danish terms for the same recurring UI action once a preferred term is established
-27
View File
@@ -1,27 +0,0 @@
# de Glossary
## Sources
- PR #9817: https://github.com/anomalyco/opencode/pull/9817
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
## Guidance
- Prefer natural German phrasing over literal translation
- Keep tone clear and direct in UI labels and docs prose
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
## Avoid
- Avoid translating product and protocol names that are fixed identifiers
- Avoid mixing multiple German terms for the same recurring UI action once a preferred term is established
-27
View File
@@ -1,27 +0,0 @@
# es Glossary
## Sources
- PR #9817: https://github.com/anomalyco/opencode/pull/9817
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
## Guidance
- Prefer natural Spanish phrasing over literal translation
- Keep tone clear and direct in UI labels and docs prose
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
## Avoid
- Avoid translating product and protocol names that are fixed identifiers
- Avoid mixing multiple Spanish terms for the same recurring UI action once a preferred term is established
-27
View File
@@ -1,27 +0,0 @@
# fr Glossary
## Sources
- PR #9821: https://github.com/anomalyco/opencode/pull/9821
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
## Guidance
- Prefer natural French phrasing over literal translation
- Keep tone clear and direct in UI labels and docs prose
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
## Avoid
- Avoid translating product and protocol names that are fixed identifiers
- Avoid mixing multiple French terms for the same recurring UI action once a preferred term is established
-33
View File
@@ -1,33 +0,0 @@
# ja Glossary
## Sources
- PR #9821: https://github.com/anomalyco/opencode/pull/9821
- PR #13160: https://github.com/anomalyco/opencode/pull/13160
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
These are PR-backed wording preferences and may evolve.
| English / Context | Preferred | Notes |
| --------------------------- | ----------------------- | ------------------------------------- |
| WSL integration (UI label) | `WSL連携` | PR #13160 prefers this over `WSL統合` |
| WSL integration description | `WindowsのWSL環境で...` | PR #13160 improved phrasing naturally |
## Guidance
- Prefer natural Japanese phrasing over literal translation
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
- In WSL integration text, follow PR #13160 wording direction for more natural Japanese phrasing
## Avoid
- Avoid `WSL統合` in the WSL integration UI context where `WSL連携` is the reviewed wording
- Avoid translating product and protocol names that are fixed identifiers
-27
View File
@@ -1,27 +0,0 @@
# ko Glossary
## Sources
- PR #9817: https://github.com/anomalyco/opencode/pull/9817
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
## Guidance
- Prefer natural Korean phrasing over literal translation
- Keep tone clear and direct in UI labels and docs prose
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
## Avoid
- Avoid translating product and protocol names that are fixed identifiers
- Avoid mixing multiple Korean terms for the same recurring UI action once a preferred term is established
-38
View File
@@ -1,38 +0,0 @@
# no Glossary
## Sources
- PR #10018: https://github.com/anomalyco/opencode/pull/10018
- PR #12935: https://github.com/anomalyco/opencode/pull/12935
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Sound names (PR #10018 notes these were intentionally left untranslated)
## Preferred Terms
These are PR-backed corrections and may evolve.
| English / Context | Preferred | Notes |
| ----------------------------------- | ------------ | ----------------------------- |
| Save (data persistence action) | `Lagre` | Prefer over `Spare` |
| Disabled (feature/state) | `deaktivert` | Prefer over `funksjonshemmet` |
| API keys | `API Nøkler` | Prefer over `API Taster` |
| Cost (noun) | `Kostnad` | Prefer over verb form `Koste` |
| Show/View (imperative button label) | `Vis` | Prefer over `Utsikt` |
## Guidance
- Prefer natural Norwegian Bokmal (Bokmål) wording over literal translation
- Keep tone clear and practical in UI labels
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
- Keep recurring UI terms consistent once a preferred term is chosen
## Avoid
- Avoid `Spare` for save actions in persistence contexts
- Avoid `funksjonshemmet` for disabled feature states
- Avoid `API Taster`, `Koste`, and `Utsikt` in the corrected contexts above
-27
View File
@@ -1,27 +0,0 @@
# pl Glossary
## Sources
- PR #9884: https://github.com/anomalyco/opencode/pull/9884
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
## Guidance
- Prefer natural Polish phrasing over literal translation
- Keep tone clear and direct in UI labels and docs prose
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
## Avoid
- Avoid translating product and protocol names that are fixed identifiers
- Avoid mixing multiple Polish terms for the same recurring UI action once a preferred term is established
-27
View File
@@ -1,27 +0,0 @@
# ru Glossary
## Sources
- PR #9882: https://github.com/anomalyco/opencode/pull/9882
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
No PR-backed term mappings yet. Add entries here when review PRs introduce repeated wording corrections.
## Guidance
- Prefer natural Russian phrasing over literal translation
- Keep tone clear and direct in UI labels and docs prose
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
## Avoid
- Avoid translating product and protocol names that are fixed identifiers
- Avoid mixing multiple Russian terms for the same recurring UI action once a preferred term is established
-34
View File
@@ -1,34 +0,0 @@
# th Glossary
## Sources
- PR #10809: https://github.com/anomalyco/opencode/pull/10809
- PR #11496: https://github.com/anomalyco/opencode/pull/11496
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only in commands, package names, paths, or code)
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
These are PR-backed preferences and may evolve.
| English / Context | Preferred | Notes |
| ------------------------------------- | --------------------- | -------------------------------------------------------------------------------- |
| Thai language label in language lists | `ไทย` | PR #10809 standardized this across locales |
| Language names in language pickers | Native names (static) | PR #11496: keep names like `English`, `Deutsch`, `ไทย` consistent across locales |
## Guidance
- Prefer natural Thai phrasing over literal translation
- Keep tone short and clear for buttons and labels
- Preserve technical artifacts exactly: commands, flags, code, URLs, model IDs, and file paths
- Keep language names static/native in language pickers instead of translating them per current locale (PR #11496)
## Avoid
- Avoid translating language names differently per current locale in language lists
- Avoid changing `ไทย` to another display form for the Thai language option unless the product standard changes
-38
View File
@@ -1,38 +0,0 @@
# tr Glossary
## Sources
- PR #15835: https://github.com/anomalyco/opencode/pull/15835
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose, docs, and UI copy)
- Keep lowercase `opencode` in commands, package names, paths, URLs, and other exact identifiers
- `<TAB>` stays the literal key token in code blocks; use `Tab` for the nearby explanatory label in prose
- Commands, flags, file paths, and code literals (keep exactly as written)
## Preferred Terms
These are PR-backed wording preferences and may evolve.
| English / Context | Preferred | Notes |
| ------------------------- | --------------------------------------- | ------------------------------------------------------------- |
| available in beta | `beta olarak mevcut` | Prefer this over `beta olarak kullanılabilir` |
| privacy-first | `Gizlilik öncelikli tasarlandı` | Prefer this over `Önce gizlilik için tasarlandı` |
| connect your local models | `yerel modellerinizi bağlayabilirsiniz` | Use the fuller, more direct action phrase |
| `<TAB>` key label | `Tab` | Use `Tab` in prose; keep `<TAB>` in literal UI or code blocks |
| cross-platform | `cross-platform (tüm platformlarda)` | Keep the English term, add a short clarification when helpful |
## Guidance
- Prefer natural Turkish phrasing over literal translation
- Merge broken sentence fragments into one clear sentence when the source is a single thought
- Keep product naming consistent: `OpenCode` in prose, `opencode` only for exact technical identifiers
- When an English technical term is intentionally kept, add a short Turkish clarification only if it improves readability
## Avoid
- Avoid `beta olarak kullanılabilir` when `beta olarak mevcut` fits
- Avoid `Önce gizlilik için tasarlandı`; use the more natural reviewed wording instead
- Avoid `Sekme` for the translated key label in prose when referring to `<TAB>`
- Avoid changing `opencode` to `OpenCode` inside commands, URLs, package names, or code literals
-42
View File
@@ -1,42 +0,0 @@
# zh-cn Glossary
## Sources
- PR #13942: https://github.com/anomalyco/opencode/pull/13942
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only when it is part of commands, package names, paths, or code)
- `OpenCode Zen`
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- `Model Context Protocol` (prefer the English expansion when introducing `MCP`)
## Preferred Terms
These are preferred terms for docs/UI prose and may evolve.
| English | Preferred | Notes |
| ----------------------- | --------- | ------------------------------------------- |
| prompt | 提示词 | Keep `--prompt` unchanged in flags/code |
| session | 会话 | |
| provider | 提供商 | |
| share link / shared URL | 分享链接 | Prefer `分享` for user-facing share actions |
| headless (server) | 无界面 | Docs wording |
| authentication | 认证 | Prefer in auth/OAuth contexts |
| cache | 缓存 | |
| keybind / shortcut | 快捷键 | User-facing docs wording |
| workflow | 工作流 | e.g. GitHub Actions workflow |
## Guidance
- Prefer natural, concise phrasing over literal translation
- Keep the tone direct and friendly (PR #13942 consistently moved wording in this direction)
- Preserve technical artifacts exactly: commands, flags, code, inline code, URLs, file paths, model IDs
- Keep enum-like values in English when they are literals (for example, `default`, `json`)
- Prefer consistent terminology across pages once a term is chosen (`会话`, `提供商`, `提示词`, etc.)
## Avoid
- Avoid `opencode` in prose when referring to the product name; use `OpenCode`
- Avoid mixing alternative terms for the same concept across docs when a preferred term is already established
-42
View File
@@ -1,42 +0,0 @@
# zh-tw Glossary
## Sources
- PR #13942: https://github.com/anomalyco/opencode/pull/13942
## Do Not Translate (Locale Additions)
- `OpenCode` (preserve casing in prose; keep `opencode` only when it is part of commands, package names, paths, or code)
- `OpenCode Zen`
- `OpenCode CLI`
- `CLI`, `TUI`, `MCP`, `OAuth`
- `Model Context Protocol` (prefer the English expansion when introducing `MCP`)
## Preferred Terms
These are preferred terms for docs/UI prose and may evolve.
| English | Preferred | Notes |
| ----------------------- | --------- | ------------------------------------------- |
| prompt | 提示詞 | Keep `--prompt` unchanged in flags/code |
| session | 工作階段 | |
| provider | 供應商 | |
| share link / shared URL | 分享連結 | Prefer `分享` for user-facing share actions |
| headless (server) | 無介面 | Docs wording |
| authentication | 認證 | Prefer in auth/OAuth contexts |
| cache | 快取 | |
| keybind / shortcut | 快捷鍵 | User-facing docs wording |
| workflow | 工作流程 | e.g. GitHub Actions workflow |
## Guidance
- Prefer natural, concise phrasing over literal translation
- Keep the tone direct and friendly (PR #13942 consistently moved wording in this direction)
- Preserve technical artifacts exactly: commands, flags, code, inline code, URLs, file paths, model IDs
- Keep enum-like values in English when they are literals (for example, `default`, `json`)
- Prefer consistent terminology across pages once a term is chosen (`工作階段`, `供應商`, `提示詞`, etc.)
## Avoid
- Avoid `opencode` in prose when referring to the product name; use `OpenCode`
- Avoid mixing alternative terms for the same concept across docs when a preferred term is already established
-13
View File
@@ -1,13 +0,0 @@
{
"$schema": "https://opencode.ai/config.json",
"provider": {},
"permission": {},
"reference": {
"effect": "github.com/Effect-TS/effect-smol",
},
"mcp": {},
"tools": {
"github-triage": false,
"github-pr-search": false,
},
}
-223
View File
@@ -1,223 +0,0 @@
{
"$schema": "https://opencode.ai/theme.json",
"defs": {
"nord0": "#2E3440",
"nord1": "#3B4252",
"nord2": "#434C5E",
"nord3": "#4C566A",
"nord4": "#D8DEE9",
"nord5": "#E5E9F0",
"nord6": "#ECEFF4",
"nord7": "#8FBCBB",
"nord8": "#88C0D0",
"nord9": "#81A1C1",
"nord10": "#5E81AC",
"nord11": "#BF616A",
"nord12": "#D08770",
"nord13": "#EBCB8B",
"nord14": "#A3BE8C",
"nord15": "#B48EAD"
},
"theme": {
"primary": {
"dark": "nord10",
"light": "nord9"
},
"secondary": {
"dark": "nord9",
"light": "nord9"
},
"accent": {
"dark": "nord7",
"light": "nord7"
},
"error": {
"dark": "nord11",
"light": "nord11"
},
"warning": {
"dark": "nord12",
"light": "nord12"
},
"success": {
"dark": "nord14",
"light": "nord14"
},
"info": {
"dark": "nord8",
"light": "nord10"
},
"text": {
"dark": "nord6",
"light": "nord0"
},
"textMuted": {
"dark": "#8B95A7",
"light": "nord1"
},
"background": {
"dark": "nord0",
"light": "nord6"
},
"backgroundPanel": {
"dark": "nord1",
"light": "nord5"
},
"backgroundElement": {
"dark": "nord2",
"light": "nord4"
},
"border": {
"dark": "nord2",
"light": "nord3"
},
"borderActive": {
"dark": "nord3",
"light": "nord2"
},
"borderSubtle": {
"dark": "nord2",
"light": "nord3"
},
"diffAdded": {
"dark": "nord14",
"light": "nord14"
},
"diffRemoved": {
"dark": "nord11",
"light": "nord11"
},
"diffContext": {
"dark": "#8B95A7",
"light": "nord3"
},
"diffHunkHeader": {
"dark": "#8B95A7",
"light": "nord3"
},
"diffHighlightAdded": {
"dark": "nord14",
"light": "nord14"
},
"diffHighlightRemoved": {
"dark": "nord11",
"light": "nord11"
},
"diffAddedBg": {
"dark": "#36413C",
"light": "#E6EBE7"
},
"diffRemovedBg": {
"dark": "#43393D",
"light": "#ECE6E8"
},
"diffContextBg": {
"dark": "nord1",
"light": "nord5"
},
"diffLineNumber": {
"dark": "nord2",
"light": "nord4"
},
"diffAddedLineNumberBg": {
"dark": "#303A35",
"light": "#DDE4DF"
},
"diffRemovedLineNumberBg": {
"dark": "#3C3336",
"light": "#E4DDE0"
},
"markdownText": {
"dark": "nord4",
"light": "nord0"
},
"markdownHeading": {
"dark": "nord8",
"light": "nord10"
},
"markdownLink": {
"dark": "nord9",
"light": "nord9"
},
"markdownLinkText": {
"dark": "nord7",
"light": "nord7"
},
"markdownCode": {
"dark": "nord14",
"light": "nord14"
},
"markdownBlockQuote": {
"dark": "#8B95A7",
"light": "nord3"
},
"markdownEmph": {
"dark": "nord12",
"light": "nord12"
},
"markdownStrong": {
"dark": "nord13",
"light": "nord13"
},
"markdownHorizontalRule": {
"dark": "#8B95A7",
"light": "nord3"
},
"markdownListItem": {
"dark": "nord8",
"light": "nord10"
},
"markdownListEnumeration": {
"dark": "nord7",
"light": "nord7"
},
"markdownImage": {
"dark": "nord9",
"light": "nord9"
},
"markdownImageText": {
"dark": "nord7",
"light": "nord7"
},
"markdownCodeBlock": {
"dark": "nord4",
"light": "nord0"
},
"syntaxComment": {
"dark": "#8B95A7",
"light": "nord3"
},
"syntaxKeyword": {
"dark": "nord9",
"light": "nord9"
},
"syntaxFunction": {
"dark": "nord8",
"light": "nord8"
},
"syntaxVariable": {
"dark": "nord7",
"light": "nord7"
},
"syntaxString": {
"dark": "nord14",
"light": "nord14"
},
"syntaxNumber": {
"dark": "nord15",
"light": "nord15"
},
"syntaxType": {
"dark": "nord7",
"light": "nord7"
},
"syntaxOperator": {
"dark": "nord9",
"light": "nord9"
},
"syntaxPunctuation": {
"dark": "nord4",
"light": "nord0"
}
}
}
File diff suppressed because it is too large Load Diff
-38
View File
@@ -1,38 +0,0 @@
---
name: effect
description: Work with Effect v4 / effect-smol TypeScript code in this repo
---
# Effect
This codebase uses Effect for typed, composable TypeScript services, schemas, and workflows.
## Source Of Truth
Use the current Effect v4 / effect-smol source, not memory or older Effect v2/v3 examples.
1. If `.opencode/references/effect-smol` is missing, clone `https://github.com/Effect-TS/effect-smol` there. Do this in the project, not in the skill folder.
2. Search `.opencode/references/effect-smol` for exact APIs, examples, tests, and naming patterns before answering or implementing Effect-specific code.
3. Also inspect existing repo code for local house style before introducing new patterns.
4. Prefer answers and implementations backed by specific source files or nearby repo examples.
## Guidelines
- Prefer current Effect v4 APIs and project-local patterns over old blog posts, examples, or package-memory guesses.
- Use `Effect.gen(function* () { ... })` for multi-step workflows.
- Use `Effect.fn("Name")` or `Effect.fnUntraced(...)` for named effects when adding reusable service methods or important workflows.
- Prefer Effect `Schema` for API and domain data shapes. Use branded schemas for IDs and `Schema.TaggedErrorClass` for typed domain errors when modeling new error surfaces.
- Keep HTTP handlers thin: decode input, read request context, call services, and map transport errors. Put business rules in services.
- In Effect service code, prefer Effect-aware platform abstractions and dependencies over ad hoc promises where the surrounding code already does so.
- Keep layer composition explicit. Avoid broad hidden provisioning that makes missing dependencies hard to see.
- In tests, prefer the repo's existing Effect test helpers and live tests for filesystem, git, child process, locks, or timing behavior.
- Do not introduce `any`, non-null assertions, unchecked casts, or older Effect APIs just to satisfy types.
- Do not answer from memory. Verify against `.opencode/references/effect-smol` or nearby code first.
## Testing Patterns
- Use `testEffect(...)` from `packages/opencode/test/lib/effect.ts` for tests that exercise Effect services, layers, runtime context, scoped resources, or platform integrations.
- Use `it.live(...)` for filesystem, git repositories, HTTP servers, sockets, child processes, locks, real time, and other live platform behavior.
- Run tests from package directories such as `packages/opencode`; never run package tests from the repo root.
- Prefer explicit test layers over ad hoc managed runtimes. Keep dependency provisioning visible in the test file.
- Use scoped fixtures and finalizers for resources that must be cleaned up, including temporary directories, flags, databases, fibers, servers, and global state.
-1
View File
@@ -1 +0,0 @@
smoke-theme.json
-223
View File
@@ -1,223 +0,0 @@
{
"$schema": "https://opencode.ai/theme.json",
"defs": {
"nord0": "#2E3440",
"nord1": "#3B4252",
"nord2": "#434C5E",
"nord3": "#4C566A",
"nord4": "#D8DEE9",
"nord5": "#E5E9F0",
"nord6": "#ECEFF4",
"nord7": "#8FBCBB",
"nord8": "#88C0D0",
"nord9": "#81A1C1",
"nord10": "#5E81AC",
"nord11": "#BF616A",
"nord12": "#D08770",
"nord13": "#EBCB8B",
"nord14": "#A3BE8C",
"nord15": "#B48EAD"
},
"theme": {
"primary": {
"dark": "nord8",
"light": "nord10"
},
"secondary": {
"dark": "nord9",
"light": "nord9"
},
"accent": {
"dark": "nord7",
"light": "nord7"
},
"error": {
"dark": "nord11",
"light": "nord11"
},
"warning": {
"dark": "nord12",
"light": "nord12"
},
"success": {
"dark": "nord14",
"light": "nord14"
},
"info": {
"dark": "nord8",
"light": "nord10"
},
"text": {
"dark": "nord4",
"light": "nord0"
},
"textMuted": {
"dark": "nord3",
"light": "nord1"
},
"background": {
"dark": "nord0",
"light": "nord6"
},
"backgroundPanel": {
"dark": "nord1",
"light": "nord5"
},
"backgroundElement": {
"dark": "nord1",
"light": "nord4"
},
"border": {
"dark": "nord2",
"light": "nord3"
},
"borderActive": {
"dark": "nord3",
"light": "nord2"
},
"borderSubtle": {
"dark": "nord2",
"light": "nord3"
},
"diffAdded": {
"dark": "nord14",
"light": "nord14"
},
"diffRemoved": {
"dark": "nord11",
"light": "nord11"
},
"diffContext": {
"dark": "nord3",
"light": "nord3"
},
"diffHunkHeader": {
"dark": "nord3",
"light": "nord3"
},
"diffHighlightAdded": {
"dark": "nord14",
"light": "nord14"
},
"diffHighlightRemoved": {
"dark": "nord11",
"light": "nord11"
},
"diffAddedBg": {
"dark": "#3B4252",
"light": "#E5E9F0"
},
"diffRemovedBg": {
"dark": "#3B4252",
"light": "#E5E9F0"
},
"diffContextBg": {
"dark": "nord1",
"light": "nord5"
},
"diffLineNumber": {
"dark": "#abafb7",
"light": "textMuted"
},
"diffAddedLineNumberBg": {
"dark": "#3B4252",
"light": "#E5E9F0"
},
"diffRemovedLineNumberBg": {
"dark": "#3B4252",
"light": "#E5E9F0"
},
"markdownText": {
"dark": "nord4",
"light": "nord0"
},
"markdownHeading": {
"dark": "nord8",
"light": "nord10"
},
"markdownLink": {
"dark": "nord9",
"light": "nord9"
},
"markdownLinkText": {
"dark": "nord7",
"light": "nord7"
},
"markdownCode": {
"dark": "nord14",
"light": "nord14"
},
"markdownBlockQuote": {
"dark": "nord3",
"light": "nord3"
},
"markdownEmph": {
"dark": "nord12",
"light": "nord12"
},
"markdownStrong": {
"dark": "nord13",
"light": "nord13"
},
"markdownHorizontalRule": {
"dark": "nord3",
"light": "nord3"
},
"markdownListItem": {
"dark": "nord8",
"light": "nord10"
},
"markdownListEnumeration": {
"dark": "nord7",
"light": "nord7"
},
"markdownImage": {
"dark": "nord9",
"light": "nord9"
},
"markdownImageText": {
"dark": "nord7",
"light": "nord7"
},
"markdownCodeBlock": {
"dark": "nord4",
"light": "nord0"
},
"syntaxComment": {
"dark": "nord3",
"light": "nord3"
},
"syntaxKeyword": {
"dark": "nord9",
"light": "nord9"
},
"syntaxFunction": {
"dark": "nord8",
"light": "nord8"
},
"syntaxVariable": {
"dark": "nord7",
"light": "nord7"
},
"syntaxString": {
"dark": "nord14",
"light": "nord14"
},
"syntaxNumber": {
"dark": "nord15",
"light": "nord15"
},
"syntaxType": {
"dark": "nord7",
"light": "nord7"
},
"syntaxOperator": {
"dark": "nord9",
"light": "nord9"
},
"syntaxPunctuation": {
"dark": "nord4",
"light": "nord0"
}
}
}
-64
View File
@@ -1,64 +0,0 @@
/// <reference path="../env.d.ts" />
import { tool } from "@opencode-ai/plugin"
async function githubFetch(endpoint: string, options: RequestInit = {}) {
const response = await fetch(`https://api.github.com${endpoint}`, {
...options,
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
Accept: "application/vnd.github+json",
"Content-Type": "application/json",
...(options.headers instanceof Headers ? Object.fromEntries(options.headers.entries()) : options.headers),
},
})
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`)
}
return response.json()
}
interface PR {
title: string
html_url: string
}
export default tool({
description: `Use this tool to search GitHub pull requests by title and description.
This tool searches PRs in the anomalyco/opencode repository and returns LLM-friendly results including:
- PR number and title
- Author
- State (open/closed/merged)
- Labels
- Description snippet
Use the query parameter to search for keywords that might appear in PR titles or descriptions.`,
args: {
query: tool.schema.string().describe("Search query for PR titles and descriptions"),
limit: tool.schema.number().describe("Maximum number of results to return").default(10),
offset: tool.schema.number().describe("Number of results to skip for pagination").default(0),
},
async execute(args) {
const owner = "anomalyco"
const repo = "opencode"
const page = Math.floor(args.offset / args.limit) + 1
const searchQuery = encodeURIComponent(`${args.query} repo:${owner}/${repo} type:pr state:open`)
const result = await githubFetch(
`/search/issues?q=${searchQuery}&per_page=${args.limit}&page=${page}&sort=updated&order=desc`,
)
if (result.total_count === 0) {
return `No PRs found matching "${args.query}"`
}
const prs = result.items as PR[]
if (prs.length === 0) {
return `No other PRs found matching "${args.query}"`
}
const formatted = prs.map((pr) => `${pr.title}\n${pr.html_url}`).join("\n\n")
return `Found ${result.total_count} PRs (showing ${prs.length}):\n\n${formatted}`
},
})
-60
View File
@@ -1,60 +0,0 @@
/// <reference path="../env.d.ts" />
import { tool } from "@opencode-ai/plugin"
const TEAM = {
tui: ["kommander", "simonklee"],
desktop_web: ["Hona", "Brendonovich"],
core: ["jlongster", "rekram1-node", "nexxeln", "kitlangton", "starptech"],
inference: ["fwang", "MrMushrooooom", "starptech"],
windows: ["Hona"],
} as const
function pick<T>(items: readonly T[]) {
return items[Math.floor(Math.random() * items.length)]!
}
function getIssueNumber(): number {
const issue = parseInt(process.env.ISSUE_NUMBER ?? "", 10)
if (!issue) throw new Error("ISSUE_NUMBER env var not set")
return issue
}
async function githubFetch(endpoint: string, options: RequestInit = {}) {
const response = await fetch(`https://api.github.com${endpoint}`, {
...options,
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
Accept: "application/vnd.github+json",
"Content-Type": "application/json",
...(options.headers instanceof Headers ? Object.fromEntries(options.headers.entries()) : options.headers),
},
})
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`)
}
return response.json()
}
export default tool({
description: `Use this tool to assign a GitHub issue.
Provide the team that should own the issue. This tool picks a random assignee from that team and does not apply labels.`,
args: {
team: tool.schema
.enum(Object.keys(TEAM) as [keyof typeof TEAM, ...(keyof typeof TEAM)[]])
.describe("The owning team"),
},
async execute(args) {
const issue = getIssueNumber()
const owner = "anomalyco"
const repo = "opencode"
const assignee = pick(TEAM[args.team])
await githubFetch(`/repos/${owner}/${repo}/issues/${issue}/assignees`, {
method: "POST",
body: JSON.stringify({ assignees: [assignee] }),
})
return `Assigned @${assignee} from ${args.team} to issue #${issue}`
},
})
-19
View File
@@ -1,19 +0,0 @@
{
"$schema": "https://opencode.ai/tui.json",
"plugin": [
[
"./plugins/tui-smoke.tsx",
{
"enabled": false,
"label": "workspace",
"keybinds": {
"smoke_modal": "ctrl+alt+m",
"smoke_screen": "ctrl+alt+o",
"smoke_screen_home": "escape,ctrl+shift+h",
"smoke_screen_modal": "ctrl+alt+m",
"smoke_dialog_close": "escape,q"
}
}
]
]
}
-51
View File
@@ -1,51 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json",
"options": {
"typeAware": true
},
"categories": {
"suspicious": "warn"
},
"rules": {
"typescript/no-base-to-string": "warn",
// Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield
"require-yield": "off",
// SolidJS uses `let ref: T | undefined` for JSX ref bindings assigned at runtime
"no-unassigned-vars": "off",
// SolidJS tracks reactive deps by reading properties inside createEffect
"no-unused-expressions": "off",
// Intentional control char matching (ANSI escapes, null byte sanitization)
"no-control-regex": "off",
// SST and plugin tools require triple-slash references
"triple-slash-reference": "off",
// Suspicious category: suppress noisy rules
// Effect's nested function* closures inherently shadow outer scope
"no-shadow": "off",
// Namespace-heavy codebase makes this too noisy
"unicorn/consistent-function-scoping": "off",
// Opinionated — .sort()/.reverse() mutation is fine in this codebase
"unicorn/no-array-sort": "off",
"unicorn/no-array-reverse": "off",
// Not relevant — this isn't a DOM event handler codebase
"unicorn/prefer-add-event-listener": "off",
// Bundler handles module resolution
"unicorn/require-module-specifiers": "off",
// postMessage target origin not relevant for this codebase
"unicorn/require-post-message-target-origin": "off",
// Side-effectful constructors are intentional in some places
"no-new": "off",
// Type-aware: catch unhandled promises
"typescript/no-floating-promises": "warn",
// Warn when spreading non-plain objects (Headers, class instances, etc.)
"typescript/no-misused-spread": "warn"
},
"options": {
"typeAware": true
},
"options": {
"typeAware": true
},
"ignorePatterns": ["**/node_modules", "**/dist", "**/.build", "**/.sst", "**/*.d.ts", "**/sdk.gen.ts"]
}
-2
View File
@@ -1,2 +0,0 @@
sst-env.d.ts
packages/desktop/src/bindings.ts
-11
View File
@@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "bun",
"request": "attach",
"name": "opencode (attach)",
"url": "ws://localhost:6499/"
}
]
}
-5
View File
@@ -1,5 +0,0 @@
{
"recommendations": [
"oven.bun-vscode"
]
}
-9
View File
@@ -1,9 +0,0 @@
{
"format_on_save": "on",
"formatter": {
"external": {
"command": "bunx",
"arguments": ["prettier", "--stdin-filepath", "{buffer_path}"]
}
}
}
-140
View File
@@ -1,140 +0,0 @@
- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
- The default branch in this repo is `dev`.
- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
## Commits and PR Titles
Use conventional commit-style messages and PR titles: `type(scope): summary`.
Valid types are `feat`, `fix`, `docs`, `chore`, `refactor`, and `test`. Scopes are optional; use the affected package or area when helpful, e.g. `core`, `opencode`, `tui`, `app`, `desktop`, `sdk`, or `plugin`.
Examples: `fix(tui): simplify thinking toggle styling`, `docs: update contributing guide`, `chore(sdk): regenerate types`.
## Style Guide
### General Principles
- Keep things in one function unless composable or reusable
- Do not extract single-use helpers preemptively. Inline the logic at the call site unless the helper is reused, hides a genuinely complex boundary, or has a clear independent name that improves the caller.
- Avoid `try`/`catch` where possible
- Avoid using the `any` type
- Use Bun APIs when possible, like `Bun.file()`
- Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity
- Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream
- In `src/config`, follow the existing self-export pattern at the top of the file (for example `export * as ConfigAgent from "./agent"`) when adding a new config module.
Reduce total variable count by inlining when a value is only used once.
```ts
// Good
const journal = await Bun.file(path.join(dir, "journal.json")).json()
// Bad
const journalPath = path.join(dir, "journal.json")
const journal = await Bun.file(journalPath).json()
```
### Destructuring
Avoid unnecessary destructuring. Use dot notation to preserve context.
```ts
// Good
obj.a
obj.b
// Bad
const { a, b } = obj
```
### Imports
- Never alias imports. Do not use `import { foo as bar } from "..."` or renamed imports like `resolve as pathResolve`.
- Never use star imports. Do not use `import * as Foo from "..."` or `import type * as Foo from "..."`.
- If a namespace-style value is needed, import the module's own exported namespace by name, for example `import { Project } from "@opencode-ai/core/project"`, then reference `Project.ID`.
- Prefer dynamic imports for heavy modules that are only needed in selected code paths, especially in startup-sensitive entrypoints. Destructure dynamic import bindings near the top of the narrowest scope that needs them so they read like normal imports. Avoid inline chains such as `await import("./module").then((mod) => mod.value())` or `(await import("./module")).value()`. Keep branch-specific imports inside the branch that needs them to preserve lazy loading.
### Variables
Prefer `const` over `let`. Use ternaries or early returns instead of reassignment.
```ts
// Good
const foo = condition ? 1 : 2
// Bad
let foo
if (condition) foo = 1
else foo = 2
```
### Control Flow
Avoid `else` statements. Prefer early returns.
```ts
// Good
function foo() {
if (condition) return 1
return 2
}
// Bad
function foo() {
if (condition) return 1
else return 2
}
```
### Complex Logic
When a function has several validation branches or supporting details, make the main function read as the happy path and move supporting details into small helpers below it.
```ts
// Good
export function loadThing(input: unknown) {
const config = requireConfig(input)
const metadata = readMetadata(input)
return createThing({ config, metadata })
}
function requireConfig(input: unknown) {
...
}
```
- Keep helpers close to the code they support, below the main export when that improves readability.
- Do not over-abstract simple expressions into many single-use helpers; extract only when it names a real concept like `requireConfig` or `readMetadata`.
- Do not return `Effect` from helpers unless they actually perform effectful work. Synchronous parsing, validation, and option building should stay synchronous.
- Prefer Effect schema helpers such as `Schema.UnknownFromJsonString` and `Schema.decodeUnknownOption` over manual `JSON.parse` wrapped in `Effect.try` when parsing untrusted JSON strings.
- Add comments for non-obvious constraints and surprising behavior, not for obvious assignments or control flow.
### Schema Definitions (Drizzle)
Use snake_case for field names so column names don't need to be redefined as strings.
```ts
// Good
const table = sqliteTable("session", {
id: text().primaryKey(),
project_id: text().notNull(),
created_at: integer().notNull(),
})
// Bad
const table = sqliteTable("session", {
id: text("id").primaryKey(),
projectID: text("project_id").notNull(),
createdAt: integer("created_at").notNull(),
})
```
## Testing
- Avoid mocks as much as possible
- Test actual implementation, do not duplicate logic into tests
- Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`.
## Type Checking
- Always run `bun typecheck` from package directories (e.g., `packages/opencode`), never `tsc` directly.
-299
View File
@@ -1,299 +0,0 @@
# Contributing to OpenCode
We want to make it easy for you to contribute to OpenCode. Here are the most common type of changes that get merged:
- Bug fixes
- Additional LSPs / Formatters
- Improvements to LLM performance
- Support for new providers
- Fixes for environment-specific quirks
- Missing standard behavior
- Documentation improvements
However, any UI or core product feature must go through a design review with the core team before implementation.
If you are unsure if a PR would be accepted, feel free to ask a maintainer or look for issues with any of the following labels:
- [`help wanted`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Ahelp-wanted)
- [`good first issue`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)
- [`bug`](https://github.com/anomalyco/opencode/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug)
- [`perf`](https://github.com/anomalyco/opencode/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22perf%22)
> [!NOTE]
> PRs that ignore these guardrails will likely be closed.
Want to take on an issue? Leave a comment and a maintainer may assign it to you unless it is something we are already working on.
## Adding New Providers
New providers shouldn't require many if ANY code changes, but if you want to add support for a new provider first make a PR to:
https://github.com/anomalyco/models.dev
## Developing OpenCode
- Requirements: Bun 1.3+
- Install dependencies and start the dev server from the repo root:
```bash
bun install
bun dev
```
### Running against a different directory
By default, `bun dev` runs OpenCode in the `packages/opencode` directory. To run it against a different directory or repository:
```bash
bun dev <directory>
```
To run OpenCode in the root of the opencode repo itself:
```bash
bun dev .
```
### Building a "localcode"
To compile a standalone executable:
```bash
./packages/opencode/script/build.ts --single
```
Then run it with:
```bash
./packages/opencode/dist/opencode-<platform>/bin/opencode
```
Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64`).
- Core pieces:
- `packages/opencode`: OpenCode core business logic & server.
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
- `packages/app`: The shared web UI components, written in SolidJS
- `packages/desktop`: The native desktop app, built with Electron (wraps `packages/app`)
- `packages/plugin`: Source for `@opencode-ai/plugin`
### Understanding bun dev vs opencode
During development, `bun dev` is the local equivalent of the built `opencode` command. Both run the same CLI interface:
```bash
# Development (from project root)
bun dev --help # Show all available commands
bun dev serve # Start headless API server
bun dev web # Start server + open web interface
bun dev <directory> # Start TUI in specific directory
# Production
opencode --help # Show all available commands
opencode serve # Start headless API server
opencode web # Start server + open web interface
opencode <directory> # Start TUI in specific directory
```
### Running the API Server
To start the OpenCode headless API server:
```bash
bun dev serve
```
This starts the headless server on port 4096 by default. You can specify a different port:
```bash
bun dev serve --port 8080
```
### Running the Web App
To test UI changes during development:
1. **First, start the OpenCode server** (see [Running the API Server](#running-the-api-server) section above)
2. **Then run the web app:**
```bash
bun run --cwd packages/app dev
```
This starts a local dev server at http://localhost:5173 (or similar port shown in output). Most UI changes can be tested here, but the server must be running for full functionality.
### Running the Desktop App
The desktop app is an Electron application that wraps the web UI.
To run the desktop app in development:
```bash
bun run --cwd packages/desktop dev
```
To create a production build and package the app:
```bash
bun run --cwd packages/desktop build
bun run --cwd packages/desktop package
```
> [!NOTE]
> If you make changes to the API or SDK (e.g. `packages/opencode/src/server/server.ts`), run `./script/generate.ts` to regenerate the SDK and related files.
Please try to follow the [style guide](./AGENTS.md)
### Setting up a Debugger
Bun debugging is currently rough around the edges. We hope this guide helps you get set up and avoid some pain points.
The most reliable way to debug OpenCode is to run it manually in a terminal via `bun run --inspect=<url> dev ...` and attach
your debugger via that URL. Other methods can result in breakpoints being mapped incorrectly, at least in VSCode (YMMV).
Caveats:
- If you want to run the OpenCode TUI and have breakpoints triggered in the server code, you might need to run `bun dev spawn` instead of
the usual `bun dev`. This is because `bun dev` runs the server in a worker thread and breakpoints might not work there.
- If `spawn` does not work for you, you can debug the server separately:
- Debug server: `bun run --inspect=ws://localhost:6499/ --cwd packages/opencode ./src/index.ts serve --port 4096`,
then attach TUI with `opencode attach http://localhost:4096`
- Debug TUI: `bun run --inspect=ws://localhost:6499/ --cwd packages/opencode --conditions=browser ./src/index.ts`
Other tips and tricks:
- You might want to use `--inspect-wait` or `--inspect-brk` instead of `--inspect`, depending on your workflow
- Specifying `--inspect=ws://localhost:6499/` on every invocation can be tiresome, you may want to `export BUN_OPTIONS=--inspect=ws://localhost:6499/` instead
#### VSCode Setup
If you use VSCode, you can use our example configurations [.vscode/settings.example.json](.vscode/settings.example.json) and [.vscode/launch.example.json](.vscode/launch.example.json).
Some debug methods that can be problematic:
- Debug configurations with `"request": "launch"` can have breakpoints incorrectly mapped and thus unusable
- The same problem arises when running OpenCode in the VSCode `JavaScript Debug Terminal`
With that said, you may want to try these methods, as they might work for you.
## Pull Request Expectations
### Issue First Policy
**All PRs must reference an existing issue.** Before opening a PR, open an issue describing the bug or feature. This helps maintainers triage and prevents duplicate work. PRs without a linked issue may be closed without review.
- Use `Fixes #123` or `Closes #123` in your PR description to link the issue
- For small fixes, a brief issue is fine - just enough context for maintainers to understand the problem
### General Requirements
- Keep pull requests small and focused
- Explain the issue and why your change fixes it
- Before adding new functionality, ensure it doesn't already exist elsewhere in the codebase
### UI Changes
If your PR includes UI changes, please include screenshots or videos showing the before and after. This helps maintainers review faster and gives you quicker feedback.
### Logic Changes
For non-UI changes (bug fixes, new features, refactors), explain **how you verified it works**:
- What did you test?
- How can a reviewer reproduce/confirm the fix?
### No AI-Generated Walls of Text
Long, AI-generated PR descriptions and issues are not acceptable and may be ignored. Respect the maintainers' time:
- Write short, focused descriptions
- Explain what changed and why in your own words
- If you can't explain it briefly, your PR might be too large
### PR Titles
PR titles should follow conventional commit standards:
- `feat:` new feature or functionality
- `fix:` bug fix
- `docs:` documentation or README changes
- `chore:` maintenance tasks, dependency updates, etc.
- `refactor:` code refactoring without changing behavior
- `test:` adding or updating tests
You can optionally include a scope to indicate which package is affected:
- `feat(app):` feature in the app package
- `fix(desktop):` bug fix in the desktop package
- `chore(opencode):` maintenance in the opencode package
Examples:
- `docs: update contributing guidelines`
- `fix: resolve crash on startup`
- `feat: add dark mode support`
- `feat(app): add dark mode support`
- `fix(desktop): resolve crash on startup`
- `chore: bump dependency versions`
### Style Preferences
These are not strictly enforced, they are just general guidelines:
- **Functions:** Keep logic within a single function unless breaking it out adds clear reuse or composition benefits.
- **Destructuring:** Do not do unnecessary destructuring of variables.
- **Control flow:** Avoid `else` statements.
- **Error handling:** Prefer `.catch(...)` instead of `try`/`catch` when possible.
- **Types:** Reach for precise types and avoid `any`.
- **Variables:** Stick to immutable patterns and avoid `let`.
- **Naming:** Choose concise single-word identifiers when they remain descriptive.
- **Runtime APIs:** Use Bun helpers such as `Bun.file()` when they fit the use case.
## Feature Requests
For net-new functionality, start with a design conversation. Open an issue describing the problem, your proposed approach (optional), and why it belongs in OpenCode. The core team will help decide whether it should move forward; please wait for that approval instead of opening a feature PR directly.
## Trust & Vouch System
This project uses [vouch](https://github.com/mitchellh/vouch) to manage contributor trust. The vouch list is maintained in [`.github/VOUCHED.td`](.github/VOUCHED.td).
### How it works
- **Vouched users** are explicitly trusted contributors.
- **Denounced users** are explicitly blocked. Issues and pull requests from denounced users are automatically closed. If you have been denounced, you can request to be unvouched by reaching out to a maintainer on [Discord](https://opencode.ai/discord)
- **Everyone else** can participate normally — you don't need to be vouched to open issues or PRs.
### For maintainers
Collaborators with write access can manage the vouch list by commenting on any issue:
- `vouch` — vouch for the issue author
- `vouch @username` — vouch for a specific user
- `denounce` — denounce the issue author
- `denounce @username` — denounce a specific user
- `denounce @username <reason>` — denounce with a reason
- `unvouch` / `unvouch @username` — remove someone from the list
Changes are committed automatically to `.github/VOUCHED.td`.
### Denouncement policy
Denouncement is reserved for users who repeatedly submit low-quality AI-generated contributions, spam, or otherwise act in bad faith. It is not used for disagreements or honest mistakes.
## Issue Requirements
All issues **must** use one of our issue templates:
- **Bug report** — for reporting bugs (requires a description)
- **Feature request** — for suggesting enhancements (requires verification checkbox and description)
- **Question** — for asking questions (requires the question)
Blank issues are not allowed. When a new issue is opened, an automated check verifies that it follows a template and meets our contributing guidelines. If an issue doesn't meet the requirements, you'll receive a comment explaining what needs to be fixed and have **2 hours** to edit the issue. After that, it will be automatically closed.
Issues may be flagged for:
- Not using a template
- Required fields left empty or filled with placeholder text
- AI-generated walls of text
- Missing meaningful content
If you believe your issue was incorrectly flagged, let a maintainer know.
+90
View File
@@ -0,0 +1,90 @@
# Neuron Soul — GKE container image
#
# Build strategy:
# 1. CI pre-downloads all artifacts from Artifact Registry into build-artifacts/
# (neuron soul binary, El compiler, El runtime). No GCP credentials are needed
# inside the build — all AR access happens in the CI workflow before docker build.
# 2. Build engram from source (neuron-technologies/engram, cloned by CI into ./engram/).
# 3. Package soul + engram in an Ubuntu 24.04 runtime image (GLIBC 2.39).
# 4. entrypoint.sh starts engram on :8742, waits for it to be healthy,
# then starts the soul with ENGRAM_URL pointing at it (HTTP mode).
#
# Expected build context layout (prepared by deploy-gke.yaml before docker build):
# build-artifacts/neuron — pre-built linux/amd64 soul binary
# build-artifacts/elc — El compiler (for engram source compilation)
# build-artifacts/el_runtime.c — El C runtime
# build-artifacts/el_runtime.h — El C runtime header
# engram/src/server.el — engram source (cloned by CI)
# entrypoint.sh — container entrypoint
#
# Required env vars (injected via ExternalSecret at runtime):
# NEURON_PORT, NEURON_LLM_0_URL, NEURON_LLM_0_KEY, NEURON_LLM_0_FORMAT,
# SOUL_CGI_ID, SOUL_IDENTITY, NEURON_TOKEN, NEURON_API_URL, ENGRAM_URL,
# ENGRAM_DATA_DIR
# ── Stage 1: Build engram from source ────────────────────────────────────────
FROM ubuntu:24.04 AS engram-builder
RUN apt-get update -qq && \
apt-get install -y --no-install-recommends \
ca-certificates \
gcc \
libc6-dev \
libcurl4-openssl-dev && \
rm -rf /var/lib/apt/lists/*
# El SDK pre-downloaded by CI into build-artifacts/
COPY build-artifacts/elc /usr/local/bin/elc
COPY build-artifacts/el_runtime.c /usr/local/lib/el/el_runtime.c
COPY build-artifacts/el_runtime.h /usr/local/lib/el/el_runtime.h
RUN chmod +x /usr/local/bin/elc
# engram source cloned by CI into ./engram/
COPY engram/src/server.el /build/src/server.el
RUN mkdir -p /build/dist && \
/usr/local/bin/elc /build/src/server.el > /build/dist/engram.c && \
echo "Compiled server.el -> engram.c ($(wc -l < /build/dist/engram.c) lines)" && \
cc -std=c11 -O2 \
-I /usr/local/lib/el \
-o /build/dist/engram \
/build/dist/engram.c \
/usr/local/lib/el/el_runtime.c \
-lcurl -lpthread -lm && \
echo "Built engram:" && ls -lh /build/dist/engram && \
chmod +x /build/dist/engram
# ── Stage 2: Runtime image ───────────────────────────────────────────────────
# Ubuntu 24.04: GLIBC 2.39 satisfies both neuron-soul and engram binary deps.
FROM ubuntu:24.04
RUN apt-get update -qq && \
apt-get install -y --no-install-recommends \
ca-certificates \
libcurl4t64 \
curl && \
rm -rf /var/lib/apt/lists/* && \
useradd -r -u 10000 -m -s /bin/bash soul
# soul binary pre-downloaded by CI into build-artifacts/
COPY build-artifacts/neuron /usr/local/bin/neuron
COPY --from=engram-builder /build/dist/engram /usr/local/bin/engram
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/neuron /usr/local/bin/engram /usr/local/bin/entrypoint.sh
# /data is the engram mount point (PVC at runtime).
RUN mkdir -p /data && chown soul:soul /data
USER soul
WORKDIR /home/soul
EXPOSE 7770
# ENGRAM_URL and ENGRAM_DATA_DIR trigger HTTP mode in the soul.
# SOUL_ENGRAM_PATH must NOT be set — its presence would enable legacy file mode.
ENV NEURON_PORT=7770 \
ENGRAM_URL=http://localhost:8742 \
ENGRAM_DATA_DIR=/data
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
+126
View File
@@ -0,0 +1,126 @@
# Handoff: Engram EL write-path field corruption + silent writes
**For:** Will (backend / EL soul)
**From:** Tim (via Claude Code)
**Date:** 2026-06-08
**Status:** Root cause confirmed; source fixes applied locally (NOT built/deployed); data analyzed; prune proposed (NOT applied).
---
## TL;DR
The EL wrapper `engram_node_full` had a **stale signature** that didn't match the C primitive. Because `el_val_t` is an untyped machine word, the compiler coerced caller args to the wrong declared types and forwarded them **by position** into a C function whose positions mean different things → `tier` got ints, `importance/confidence` got strings, `label` got a float, etc. One caller (`chat.el`) also put a *tier* into the `node_type` slot.
Source fixes are done. **You need to:** review, build with `elc`, restart the soul, verify, and apply the prune (daemon stopped). Details below.
---
## 1. Root cause (confirmed)
**C contract** (`el/lang/el-compiler/runtime/el_seed.h:204`):
```
__engram_node_full(content, node_type, label, salience, importance, confidence, tier, tags)
```
**Old wrapper** (`el/lang/runtime/engram.el:15-17`) — stale schema, wrong names AND types:
```
fn engram_node_full(content: String, nt: String, sal: Float, imp: Float,
source: String, lang: String, ts: Int, tags: String)
```
**Coercion mechanism:** `el_val_t` is `uintptr_t` (`#define EL_STR(s) ((el_val_t)(uintptr_t)(s))`, `EL_INT(v) (v)`). The EL compiler binds each caller arg to the wrapper's *declared* param type (String→Float / String→Int coercion at the boundary), then the wrapper forwards **positionally**. Result for a correct-order caller `(content,"Memory","memory:remembered",sal,imp,conf,tier,tags)`:
- `label``sal` (a float)
- `importance` ← a String
- `confidence` ← a String
- `tier``ts` (the tier String coerced to Int) → **tier becomes an integer**
This matches the data exactly (see §6).
---
## 2. Fix applied — wrapper (`el/lang/runtime/engram.el`)
Corrected to match the C contract 1:1 (no coercion, no reorder):
```
fn engram_node_full(content: String, node_type: String, label: String,
salience: Float, importance: Float, confidence: Float,
tier: String, tags: String) -> String {
// validation (see §4), then:
return __engram_node_full(content, node_type, label, salience, importance, confidence, tier, tags)
}
```
## 3. Fix applied — caller audit
Audited every caller (`chat.el`, `awareness.el`, `soul.el`, `memory.el`, `routes.el`, `neuron-api.el`).
**All `engram_node_full` callers already use the correct order** — so the wrapper fix repairs them automatically. **One real caller bug** fixed:
`neuron/chat.el:512` was:
```
engram_node(clean_response, "episodic", el_from_float(0.6)) // "episodic" = a TIER in the node_type slot
```
Now:
```
engram_node_full(clean_response, "Conversation", "soul:utterance",
el_from_float(0.6), el_from_float(0.6), el_from_float(0.8),
"Episodic", utterance_tags)
```
## 4. Fix applied — validation (defense in depth, `engram.el`)
Added `engram_valid_node_type` / `engram_valid_tier` allowlists. Both `engram_node` and `engram_node_full` now **reject invalid values with `__println` + return `""`** (fail loud, never silently write a malformed node).
- node_type allowlist: Memory, Knowledge, Belief, Project, Tag, BacklogItem, Artifact, Conversation, ExecutionContext, InternalStateEvent, Self, Entity, Process, ConfigEntry, Concept, Imprint *(union of the spec list + types actually present in the store — trim if some are illegitimate).*
- tier allowlist: Semantic, Episodic, Working, Procedural, Canonical, Note, Lesson
- **Note:** `el_val_t` is untyped, so this catches wrong VALUES, not wrong TYPES. Type safety comes from the corrected signatures.
> All edits above are in the working tree on Tim's machine but **NOT compiled/deployed** and **NOT compile-verified** (no `elc` on that box).
---
## 5. DEPLOY RUNBOOK (your build env)
1. Pull the edited files: `el/lang/runtime/engram.el`, `neuron/chat.el`.
2. Build: `elc` (entry `neuron/soul.el`, import chain) → `neuron/dist/*.c`, then link as in `el/lang/install.sh` (`$(CC) $(CFLAGS) -o dist/neuron-fresh dist/*.c .../el_runtime.c -lcurl -lpthread`). Confirm `engram.el` recompiles into the import chain.
3. Restart the soul. **Note:** on Tim's box it's run by `/tmp/soul-keepalive.sh` (an auto-restart loop) → stop that loop before killing `neuron-fresh`, or it'll respawn the old binary.
4. **Verify (prove end-to-end):** write a node via the live API (POST `/api/memories` or the remember path) with an obvious throwaway label, then read it back and confirm `node_type` + `tier` are correct AND that it persisted (node_count increments; survives a snapshot save). There is **no delete endpoint** — clean up via the snapshot.
---
## 6. Data analysis + prune proposal (NOT applied)
- Snapshot: `~/.neuron/engram/snapshot.json`. **Backup made:** `~/.neuron/engram/snapshot.backup-20260608.json`.
- **~107 corrupt nodes** (node_type/tier not in the valid sets). node_type junk values: `''`, `'1'`, `'2'`, `'ntn-genesis'`, `'claude-opus-4-8'`, binary. tier junk: same + `'/Users/timlingo'`.
- **0 are field-repairable.** They're all genesis-bootstrap / binary detritus where *every* field (id/label/tier/tags) is corrupted together — 69× "You are ntn-genesis, a CGI.", 62× "ntn-genesis", ~70 binary garbage, plus a proxy URL + an API path that leaked into labels. No signal to reconstruct → **prune, don't fabricate.**
- **Proposal:** `~/.neuron/engram/snapshot.pruned.json` — 3,631 clean nodes (107 junk removed), edges intact (no dangling). Byte-verified: no *clean* node contains binary content, so re-encoding is lossless.
- **NOT applied** because the live daemon is **actively rewriting `snapshot.json`** (two reads returned different counts). Applying requires stopping the soul + keepalive, swapping in the pruned snapshot, then restarting. Do this in your controlled env with the backup retained.
---
## 7. Security heads-up (please action)
- `ANTHROPIC_API_KEY` is stored **in plaintext** in `/tmp/soul-keepalive.sh` — rotate it and move to a secret store.
- Internal infra leaked into node fields (`http://localhost:7771`, `/api/graph/edges?limit=5000`) — symptom of the same write bug; the prune removes those nodes.
## 8. Backlog of related gaps (separate from this fix)
- Soul chat loop reports **no tools** (`NONE`) / `NO_SHELL` — it narrates `curl`/`sqlite3` without executing. The capture REST path works, but the chat agent can't call it.
- **No `PUT`/`DELETE`** on knowledge nodes (`method not allowed`) — needed for UI edit/delete.
- No **source-conversation** edge on captured nodes — blocks "see source chat" in the UI.
- Writes have been **frozen since ~2026-04-29** (newest knowledge node) — nothing is being added in the current running state.
---
## ADDENDUM — Phase 0 live runtime findings (2026-06-08, verified against the running system)
Validated the write path end-to-end against `neuron-fresh :7770` + `engram :8742`. Confirms the diagnosis and corrects two common assumptions.
**Ports:** `engram :8742` ✓ listening (healthy: `{"status":"ok","engine":"engram-runtime-native"}`), `neuron-fresh :7770` ✓, **`:7771` NOT listening.**
**Two distinct write failures (not one):**
1. **`/api/neuron/knowledge/capture` + memory remember** — handled **in-process by the soul** (`neuron-api.el` `handle_api_capture_knowledge` / remember → `engram_node_full(...)`). Live test: `POST …/knowledge/capture` returned `{"id":"2ccfc147…","ok":true}` but that id is **absent from `/api/graph/nodes` and `snapshot.json`** → the node corrupted/vanished. **This is exactly the `engram_node_full` wrapper bug this PR fixes.** It is NOT a `:7771` issue. → fixed by el PR #52 + soul rebuild.
2. **`/api/backlog`, `/api/memories`, `/api/knowledge`, `/api/artifacts`, `/api/projects`, `/api/imprints`** — `routes.el` proxies these to **`axon`** via `axon_get`/`axon_post` (base `SOUL_AXON` or default **`http://localhost:7771`**). `axon` = **`protocols/axon`, an unbuilt Rust crate**, not running → "Failed to connect to localhost port 7771." → needs axon stood up (separate Rust workstream) OR routes repointed.
**Architecture clarifications (so nobody chases the wrong port again):**
- The soul runs in **file-snapshot mode** (no `ENGRAM_URL` in `/tmp/soul-keepalive.sh`) → it uses `~/.neuron/engram/snapshot.json`, **not `engram :8742` live**. So writing to `:8742` does NOT make data visible to the soul the app talks to.
- `engram :8742` is its own EL service (`engram/src/server.el`) with a **working CRUD API**: `POST/GET/DELETE /api/nodes`, `/api/edges`, `/api/save`, `/api/load`, `/api/activate`, `/api/search`. Verified create+delete (`{"ok":true}`). **But** its `route_create_node` only reads `content/node_type/salience`**no label/tier/tags/metadata** — so it can't set `metadata.tier_source: canonical`.
- Minor EL bug in `engram/src/server.el route_create_node`: `if str_eq(node_type,""){ let node_type = "Memory" }` **shadows** (new local) instead of reassigning → the default never applies; same for `salience`. Worth fixing while in there.
**Verification plan (run after the soul rebuild lands):**
1. `POST /api/neuron/knowledge/capture {content,title,tier:canonical}` → capture the returned id.
2. `GET /api/neuron/knowledge/search?q=<term>` → confirm the node comes back with correct `node_type`/`metadata.tier_source`.
3. Confirm it survives a snapshot save (present in `snapshot.json`). Only then is the write "real."
4. Backlog: once `axon :7771` is up, repeat for `POST /api/backlog`.
**Net:** "make writes persist" needs (a) **this wrapper fix built into the soul** (capture) and (b) **`axon :7771` running** (backlog/artifacts/etc.). Neither was doable on Tim's box (no `elc`; `axon` is unbuilt Rust — out of scope per the no-Rust guardrail). No live writes/restarts were performed; engram probe node was created and deleted to verify the API.
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2025 opencode
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+184
View File
@@ -0,0 +1,184 @@
# Memory Recall Bug — Handoff for Will
**Reported by:** Tim (via the Neuron UI chat)
**Diagnosed by:** Claude (Claude Code session), 2026-06-05
**Symptom:** The soul can't recall anything specific — e.g. "do you remember the jokes
from that night with Will, Tim, and April?" → it has no idea, and correctly self-reports
that either retrieval is failing or the memory was never captured.
---
## TL;DR
The memories are almost certainly **intact in the graph**. The problem is the
**retrieval layer**: `engram_search_json` and `engram_activate_json` return empty for
*every* query, so the chat falls back to two hardcoded pinned nodes and effectively
remembers nothing. Strongly looks like the **embedding / search index was never built or
isn't loaded at boot**.
Separately: the **soul daemon on :7770 was down** at the end of the investigation (it had
been up earlier in the session — it died/stopped partway through). Restart needed before
any of this can be re-tested.
---
## Evidence
All commands run against the live services during the session.
### Search/activate return nothing — even for guaranteed-present terms
```
curl "http://127.0.0.1:8742/api/search?q=MUDCraft&limit=3" -H "X-API-Key: ntn-user-2026" → []
curl "http://127.0.0.1:8742/api/search?q=neuron&limit=3" -H "X-API-Key: ntn-user-2026" → []
curl "http://127.0.0.1:8742/api/search?q=Will&limit=3" -H "X-API-Key: ntn-user-2026" → []
curl "http://127.0.0.1:8742/api/activate?q=jokes&depth=3" -H "X-API-Key: ntn-user-2026" → {"results":[]}
# soul's in-process equivalents (port 7770) — also empty:
curl "http://127.0.0.1:7770/api/neuron/recall?query=neuron" → (empty)
curl "http://127.0.0.1:7770/api/neuron/knowledge/search?q=MUDCraft" → (empty)
```
### But the raw data is present
```
curl "http://127.0.0.1:7770/api/graph/nodes?limit=2"
→ [{"id":"mem-30425134-...","content":"CGI ARCHITECTURE ? THREE LAYERS, MCP RETIRED ...
```
`/api/graph/nodes` is served by `engram_scan_nodes_json(9999, 0)` (routes.el:223-224) and
returns hundreds of rich nodes. So node storage is fine — only the **search/activation
index** is dead.
### The two standalone-engram counters
```
curl "http://127.0.0.1:8742/api/stats" → {"node_count":0,"edge_count":0,"layer_count":5}
```
Note: the standalone engram process on :8742 reports **0 nodes**, while the soul's
in-process engram (:7770) has the data. Worth confirming which engram instance is the
source of truth and whether they've diverged. (The `:8742` process was also showing up as
`engram --help` in `ps`, which is suspicious — may not be a real server instance.)
---
## Root cause (where it breaks in code)
`neuron/chat.el → engram_compile(intent)` (lines 15-53) builds the entire memory context
for every chat turn from exactly two sources:
```el
let activate_json: String = engram_activate_json(intent, 5) // returns []
let search_json: String = engram_search_json(intent, 15) // returns []
```
When **both are empty**, it falls back to two hardcoded nodes by literal ID
(chat.el:29-41):
```el
// "Fallback: when vector search returns nothing (no embeddings), fetch pinned
// high-salience nodes by their known IDs."
let family_node = engram_get_node_json("knw-35940684-abc4-42f0-b942-818f66b1f69a")
let origin_node = engram_get_node_json("knw-729fc901-8335-44c4-9f3a-b150b4aa0915")
```
So today the soul's *entire* recallable memory in a chat = those two nodes. That's why it
can't surface jokes, social moments, the dynamic with Tim/April, or anything else specific.
The comment ("when vector search returns nothing (no embeddings)") is the key hint: this
fallback was written *expecting* the embedding index to sometimes be absent — and right
now it's absent **all the time**.
Affected callers all funnel through the same two dead builtins:
- `handle_api_recall` (neuron-api.el:118) — `engram_search_json`
- `handle_api_search_knowledge` (neuron-api.el:135) — `engram_search_json` + `engram_activate_json`
- `engram_compile` (chat.el:15) — both
Working callers use a *different* builtin (`engram_scan_nodes_json` /
`engram_scan_nodes_by_type_json`), which is why graph/list views work but recall doesn't.
---
## Fix options (Will's call)
### Option 1 — Proper fix: rebuild/restore the embedding + activation index
`engram_search_json` and `engram_activate_json` are native runtime builtins. They're
returning empty because (most likely) the vector/search index was never built or isn't
loaded at boot, even though node storage loads fine. Investigate the engram boot path:
does it build embeddings for loaded nodes? Is there an index file that's missing/stale?
Fixing this restores recall everywhere at once. **This is the real fix.**
### Option 2 — Pragmatic EL-level fallback (no native changes)
Since `engram_scan_nodes_json()` works, `engram_compile` could do a keyword scan when the
vector path is empty: pull nodes, substring/token match the query against `content` +
`label`, rank by overlap, return the top N. Restores basic recall even with the vector
index down. ~20 lines of EL in `engram_compile`, but requires a soul rebuild + restart.
Claude offered to write this patch for your review if you want it — say the word.
Tradeoff: keyword matching is much weaker than semantic recall (won't find "jokes" unless
the node text literally contains joke-ish words), but it's strictly better than the current
two-node fallback and needs no native/runtime work.
---
## Also needs attention
- **Soul daemon (:7770) was down** at end of session — restart and confirm it stays up.
- **Confirm the engram instance topology** — :8742 standalone shows 0 nodes while the
soul's in-process engram has the data. Make sure chat is reading the populated one and
they haven't diverged.
- **Social memory weighting** (Tim's deeper point): even once retrieval works, jokes /
interpersonal moments may not be tagged or salience-weighted to surface as "important."
Worth a look at how those get captured and scored — but that's secondary to getting
retrieval working at all.
---
## Daemon lifecycle — needs a supervisor (NEW, 2026-06-06)
The soul daemon **crashed again** the next day. It had been up earlier, then died on its
own (not from any change). When it's down, the UI's Backlog / Artifacts / Knowledge /
Graph / Memories tabs all go **blank**, because they read from `:7770/api/graph/nodes`.
The chat also stops working. This is the second unexplained death in two days.
### How it's currently run (fragile)
- Binary: `neuron/dist/neuron-fresh` (compiled from the EL sources)
- Launched manually as a bare background process (`./neuron-fresh &`) — **no supervisor,
no auto-restart, no crash logging beyond stdout**. When it dies, it stays dead until a
human notices the blank UI and restarts it.
- Boot log only shows `[http] listening on [::]:7770` — there's no captured stack/exit
reason when it crashes, so we can't yet say *why* it's dying.
### How I restarted it (for reference)
```sh
# snapshot lives at ~/.neuron/engram/snapshot.json (loaded on boot, ~9.7MB)
# ALWAYS back it up first — genesis boot re-saves it:
cp ~/.neuron/engram/snapshot.json ~/.neuron/engram/snapshot.backup-$(date +%Y%m%d-%H%M%S).json
cd neuron/dist
ANTHROPIC_API_KEY='<key>' NEURON_PORT=7770 ./neuron-fresh > /tmp/soul-restart.log 2>&1 &
# verify:
curl -s http://127.0.0.1:7770/health
# → {"status":"alive","cgi_id":"ntn-genesis","boot":2,"node_count":3660,"edge_count":14207,...}
```
After this, data came back: 3,660 nodes / 14,207 edges; Backlog 485, Memory 493, etc.
### Recommendations for Will
1. **Put it under a supervisor** so it auto-restarts on crash and logs exit codes:
- macOS dev: a `launchd` LaunchAgent plist (KeepAlive=true), or `brew services`, or
even a simple `while true; do ./neuron-fresh; done` wrapper with timestamped logs.
- Prod/k8s already has `entrypoint.sh` + restart policy — the gap is the **local dev**
run path.
2. **Capture crash diagnostics** — redirect stdout/stderr to a rotating logfile and, if the
EL runtime can, dump a reason on exit. Right now we're blind to the cause.
3. **Find the root cause of the crashes** — two self-deaths in two days suggests a real bug
(memory? an unhandled request? a panic in a native builtin?). The supervisor stops the
*symptom* (blank UI) but not the underlying instability.
4. **Snapshot safety** — genesis boot calls `engram_save(snapshot)` (soul.el:240,248). A
crash mid-save could corrupt the 9.7MB memory file. Consider write-to-temp + atomic
rename, and/or periodic timestamped backups, so a bad save can't lose Neuron's memory.
---
## What was NOT touched
No backend EL code and no engram data were modified — the memory-recall diagnosis is
read-only. The only operational action taken was **restarting the already-existing
`neuron-fresh` daemon** (after backing up the snapshot) to bring the blank UI tabs back;
no source or data was changed by that. All UI work this session was in `neuron-ui` and is
unrelated to this bug.
-129
View File
@@ -1,129 +0,0 @@
<p align="center">
<a href="https://opencode.ai">
<picture>
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="شعار OpenCode">
</picture>
</a>
</p>
<p align="center">وكيل برمجة بالذكاء الاصطناعي مفتوح المصدر.</p>
<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
</p>
<p align="center">
<a href="README.md">English</a> |
<a href="README.zh.md">简体中文</a> |
<a href="README.zht.md">繁體中文</a> |
<a href="README.ko.md">한국어</a> |
<a href="README.de.md">Deutsch</a> |
<a href="README.es.md">Español</a> |
<a href="README.fr.md">Français</a> |
<a href="README.it.md">Italiano</a> |
<a href="README.da.md">Dansk</a> |
<a href="README.ja.md">日本語</a> |
<a href="README.pl.md">Polski</a> |
<a href="README.ru.md">Русский</a> |
<a href="README.bs.md">Bosanski</a> |
<a href="README.ar.md">العربية</a> |
<a href="README.no.md">Norsk</a> |
<a href="README.br.md">Português (Brasil)</a> |
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a> |
<a href="README.vi.md">Tiếng Việt</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
---
### التثبيت
```bash
# YOLO
curl -fsSL https://opencode.ai/install | bash
# مديري الحزم
npm i -g opencode-ai@latest # او bun/pnpm/yarn
scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS و Linux (موصى به، دائما محدث)
brew install opencode # macOS و Linux (صيغة brew الرسمية، تحديث اقل)
sudo pacman -S opencode # Arch Linux (Stable)
paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # اي نظام
nix run nixpkgs#opencode # او github:anomalyco/opencode لاحدث فرع dev
```
> [!TIP]
> احذف الاصدارات الاقدم من 0.1.x قبل التثبيت.
### تطبيق سطح المكتب (BETA)
يتوفر OpenCode ايضا كتطبيق سطح مكتب. قم بالتنزيل مباشرة من [صفحة الاصدارات](https://github.com/anomalyco/opencode/releases) او من [opencode.ai/download](https://opencode.ai/download).
| المنصة | التنزيل |
| --------------------- | ---------------------------------- |
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
| Windows | `opencode-desktop-windows-x64.exe` |
| Linux | `.deb` او `.rpm` او AppImage |
```bash
# macOS (Homebrew)
brew install --cask opencode-desktop
# Windows (Scoop)
scoop bucket add extras; scoop install extras/opencode-desktop
```
#### مجلد التثبيت
يحترم سكربت التثبيت ترتيب الاولوية التالي لمسار التثبيت:
1. `$OPENCODE_INSTALL_DIR` - مجلد تثبيت مخصص
2. `$XDG_BIN_DIR` - مسار متوافق مع مواصفات XDG Base Directory
3. `$HOME/bin` - مجلد الثنائيات القياسي للمستخدم (ان وجد او امكن انشاؤه)
4. `$HOME/.opencode/bin` - المسار الافتراضي الاحتياطي
```bash
# امثلة
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
```
### Agents
يتضمن OpenCode وكيليْن (Agents) مدمجين يمكنك التبديل بينهما باستخدام زر `Tab`.
- **build** - الافتراضي، وكيل بصلاحيات كاملة لاعمال التطوير
- **plan** - وكيل للقراءة فقط للتحليل واستكشاف الكود
- يرفض تعديل الملفات افتراضيا
- يطلب الاذن قبل تشغيل اوامر bash
- مثالي لاستكشاف قواعد كود غير مألوفة او لتخطيط التغييرات
بالاضافة الى ذلك يوجد وكيل فرعي **general** للبحث المعقد والمهام متعددة الخطوات.
يستخدم داخليا ويمكن استدعاؤه بكتابة `@general` في الرسائل.
تعرف على المزيد حول [agents](https://opencode.ai/docs/agents).
### التوثيق
لمزيد من المعلومات حول كيفية ضبط OpenCode، [**راجع التوثيق**](https://opencode.ai/docs).
### المساهمة
اذا كنت مهتما بالمساهمة في OpenCode، يرجى قراءة [contributing docs](./CONTRIBUTING.md) قبل ارسال pull request.
### البناء فوق OpenCode
اذا كنت تعمل على مشروع مرتبط بـ OpenCode ويستخدم "opencode" كجزء من اسمه (مثل "opencode-dashboard" او "opencode-mobile")، يرجى اضافة ملاحظة في README توضح انه ليس مبنيا بواسطة فريق OpenCode ولا يرتبط بنا بأي شكل.
---
**انضم الى مجتمعنا** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
-129
View File
@@ -1,129 +0,0 @@
<p align="center">
<a href="https://opencode.ai">
<picture>
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
</picture>
</a>
</p>
<p align="center">ওপেন সোর্স এআই কোডিং এজেন্ট।</p>
<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
</p>
<p align="center">
<a href="README.md">English</a> |
<a href="README.zh.md">简体中文</a> |
<a href="README.zht.md">繁體中文</a> |
<a href="README.ko.md">한국어</a> |
<a href="README.de.md">Deutsch</a> |
<a href="README.es.md">Español</a> |
<a href="README.fr.md">Français</a> |
<a href="README.it.md">Italiano</a> |
<a href="README.da.md">Dansk</a> |
<a href="README.ja.md">日本語</a> |
<a href="README.pl.md">Polski</a> |
<a href="README.ru.md">Русский</a> |
<a href="README.bs.md">Bosanski</a> |
<a href="README.ar.md">العربية</a> |
<a href="README.no.md">Norsk</a> |
<a href="README.br.md">Português (Brasil)</a> |
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a> |
<a href="README.vi.md">Tiếng Việt</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
---
### ইনস্টলেশন (Installation)
```bash
# YOLO
curl -fsSL https://opencode.ai/install | bash
# Package managers
npm i -g opencode-ai@latest # or bun/pnpm/yarn
scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
brew install opencode # macOS and Linux (official brew formula, updated less)
sudo pacman -S opencode # Arch Linux (Stable)
paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # Any OS
nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
```
> [!TIP]
> ইনস্টল করার আগে ০.১.x এর চেয়ে পুরোনো ভার্সনগুলো মুছে ফেলুন।
### ডেস্কটপ অ্যাপ (BETA)
OpenCode ডেস্কটপ অ্যাপ্লিকেশন হিসেবেও উপলব্ধ। সরাসরি [রিলিজ পেজ](https://github.com/anomalyco/opencode/releases) অথবা [opencode.ai/download](https://opencode.ai/download) থেকে ডাউনলোড করুন।
| প্ল্যাটফর্ম | ডাউনলোড |
| --------------------- | ---------------------------------- |
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
| Windows | `opencode-desktop-windows-x64.exe` |
| Linux | `.deb`, `.rpm`, or `.AppImage` |
```bash
# macOS (Homebrew)
brew install --cask opencode-desktop
# Windows (Scoop)
scoop bucket add extras; scoop install extras/opencode-desktop
```
#### ইনস্টলেশন ডিরেক্টরি (Installation Directory)
ইনস্টল স্ক্রিপ্টটি ইনস্টলেশন পাতের জন্য নিম্নলিখিত অগ্রাধিকার ক্রম মেনে চলে:
1. `$OPENCODE_INSTALL_DIR` - কাস্টম ইনস্টলেশন ডিরেক্টরি
2. `$XDG_BIN_DIR` - XDG বেস ডিরেক্টরি স্পেসিফিকেশন সমর্থিত পাথ
3. `$HOME/bin` - সাধারণ ব্যবহারকারী বাইনারি ডিরেক্টরি (যদি বিদ্যমান থাকে বা তৈরি করা যায়)
4. `$HOME/.opencode/bin` - ডিফল্ট ফলব্যাক
```bash
# উদাহরণ
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
```
### এজেন্টস (Agents)
OpenCode এ দুটি বিল্ট-ইন এজেন্ট রয়েছে যা আপনি `Tab` কি(key) দিয়ে পরিবর্তন করতে পারবেন।
- **build** - ডিফল্ট, ডেভেলপমেন্টের কাজের জন্য সম্পূর্ণ অ্যাক্সেসযুক্ত এজেন্ট
- **plan** - বিশ্লেষণ এবং কোড এক্সপ্লোরেশনের জন্য রিড-ওনলি এজেন্ট
- ডিফল্টভাবে ফাইল এডিট করতে দেয় না
- ব্যাশ কমান্ড চালানোর আগে অনুমতি চায়
- অপরিচিত কোডবেস এক্সপ্লোর করা বা পরিবর্তনের পরিকল্পনা করার জন্য আদর্শ
এছাড়াও জটিল অনুসন্ধান এবং মাল্টিস্টেপ টাস্কের জন্য একটি **general** সাবএজেন্ট অন্তর্ভুক্ত রয়েছে।
এটি অভ্যন্তরীণভাবে ব্যবহৃত হয় এবং মেসেজে `@general` লিখে ব্যবহার করা যেতে পারে।
এজেন্টদের সম্পর্কে আরও জানুন: [docs](https://opencode.ai/docs/agents)।
### ডকুমেন্টেশন (Documentation)
কিভাবে OpenCode কনফিগার করবেন সে সম্পর্কে আরও তথ্যের জন্য, [**আমাদের ডকস দেখুন**](https://opencode.ai/docs)।
### অবদান (Contributing)
আপনি যদি OpenCode এ অবদান রাখতে চান, অনুগ্রহ করে একটি পুল রিকোয়েস্ট সাবমিট করার আগে আমাদের [কন্ট্রিবিউটিং ডকস](./CONTRIBUTING.md) পড়ে নিন।
### OpenCode এর উপর বিল্ডিং (Building on OpenCode)
আপনি যদি এমন প্রজেক্টে কাজ করেন যা OpenCode এর সাথে সম্পর্কিত এবং প্রজেক্টের নামের অংশ হিসেবে "opencode" ব্যবহার করেন, উদাহরণস্বরূপ "opencode-dashboard" বা "opencode-mobile", তবে দয়া করে আপনার README তে একটি নোট যোগ করে স্পষ্ট করুন যে এই প্রজেক্টটি OpenCode দল দ্বারা তৈরি হয়নি এবং আমাদের সাথে এর কোনো সরাসরি সম্পর্ক নেই।
---
**আমাদের কমিউনিটিতে যুক্ত হোন** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
-129
View File
@@ -1,129 +0,0 @@
<p align="center">
<a href="https://opencode.ai">
<picture>
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="Logo do OpenCode">
</picture>
</a>
</p>
<p align="center">O agente de programação com IA de código aberto.</p>
<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
</p>
<p align="center">
<a href="README.md">English</a> |
<a href="README.zh.md">简体中文</a> |
<a href="README.zht.md">繁體中文</a> |
<a href="README.ko.md">한국어</a> |
<a href="README.de.md">Deutsch</a> |
<a href="README.es.md">Español</a> |
<a href="README.fr.md">Français</a> |
<a href="README.it.md">Italiano</a> |
<a href="README.da.md">Dansk</a> |
<a href="README.ja.md">日本語</a> |
<a href="README.pl.md">Polski</a> |
<a href="README.ru.md">Русский</a> |
<a href="README.bs.md">Bosanski</a> |
<a href="README.ar.md">العربية</a> |
<a href="README.no.md">Norsk</a> |
<a href="README.br.md">Português (Brasil)</a> |
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a> |
<a href="README.vi.md">Tiếng Việt</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
---
### Instalação
```bash
# YOLO
curl -fsSL https://opencode.ai/install | bash
# Gerenciadores de pacotes
npm i -g opencode-ai@latest # ou bun/pnpm/yarn
scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS e Linux (recomendado, sempre atualizado)
brew install opencode # macOS e Linux (fórmula oficial do brew, atualiza menos)
sudo pacman -S opencode # Arch Linux (Stable)
paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # qualquer sistema
nix run nixpkgs#opencode # ou github:anomalyco/opencode para a branch dev mais recente
```
> [!TIP]
> Remova versões anteriores a 0.1.x antes de instalar.
### App desktop (BETA)
O OpenCode também está disponível como aplicativo desktop. Baixe diretamente pela [página de releases](https://github.com/anomalyco/opencode/releases) ou em [opencode.ai/download](https://opencode.ai/download).
| Plataforma | Download |
| --------------------- | ---------------------------------- |
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
| Windows | `opencode-desktop-windows-x64.exe` |
| Linux | `.deb`, `.rpm` ou AppImage |
```bash
# macOS (Homebrew)
brew install --cask opencode-desktop
# Windows (Scoop)
scoop bucket add extras; scoop install extras/opencode-desktop
```
#### Diretório de instalação
O script de instalação respeita a seguinte ordem de prioridade para o caminho de instalação:
1. `$OPENCODE_INSTALL_DIR` - Diretório de instalação personalizado
2. `$XDG_BIN_DIR` - Caminho compatível com a especificação XDG Base Directory
3. `$HOME/bin` - Diretório binário padrão do usuário (se existir ou puder ser criado)
4. `$HOME/.opencode/bin` - Fallback padrão
```bash
# Exemplos
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
```
### Agents
O OpenCode inclui dois agents integrados, que você pode alternar com a tecla `Tab`.
- **build** - Padrão, agent com acesso total para trabalho de desenvolvimento
- **plan** - Agent somente leitura para análise e exploração de código
- Nega edições de arquivos por padrão
- Pede permissão antes de executar comandos bash
- Ideal para explorar codebases desconhecidas ou planejar mudanças
Também há um subagent **general** para buscas complexas e tarefas em várias etapas.
Ele é usado internamente e pode ser invocado com `@general` nas mensagens.
Saiba mais sobre [agents](https://opencode.ai/docs/agents).
### Documentação
Para mais informações sobre como configurar o OpenCode, [**veja nossa documentação**](https://opencode.ai/docs).
### Contribuir
Se você tem interesse em contribuir com o OpenCode, leia os [contributing docs](./CONTRIBUTING.md) antes de enviar um pull request.
### Construindo com OpenCode
Se você estiver trabalhando em um projeto relacionado ao OpenCode e estiver usando "opencode" como parte do nome (por exemplo, "opencode-dashboard" ou "opencode-mobile"), adicione uma nota no README para deixar claro que não foi construído pela equipe do OpenCode e não é afiliado a nós de nenhuma forma.
---
**Junte-se à nossa comunidade** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
-129
View File
@@ -1,129 +0,0 @@
<p align="center">
<a href="https://opencode.ai">
<picture>
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
</picture>
</a>
</p>
<p align="center">OpenCode je open source AI agent za programiranje.</p>
<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
</p>
<p align="center">
<a href="README.md">English</a> |
<a href="README.zh.md">简体中文</a> |
<a href="README.zht.md">繁體中文</a> |
<a href="README.ko.md">한국어</a> |
<a href="README.de.md">Deutsch</a> |
<a href="README.es.md">Español</a> |
<a href="README.fr.md">Français</a> |
<a href="README.it.md">Italiano</a> |
<a href="README.da.md">Dansk</a> |
<a href="README.ja.md">日本語</a> |
<a href="README.pl.md">Polski</a> |
<a href="README.ru.md">Русский</a> |
<a href="README.bs.md">Bosanski</a> |
<a href="README.ar.md">العربية</a> |
<a href="README.no.md">Norsk</a> |
<a href="README.br.md">Português (Brasil)</a> |
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a> |
<a href="README.vi.md">Tiếng Việt</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
---
### Instalacija
```bash
# YOLO
curl -fsSL https://opencode.ai/install | bash
# Package manageri
npm i -g opencode-ai@latest # ili bun/pnpm/yarn
scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS i Linux (preporučeno, uvijek ažurno)
brew install opencode # macOS i Linux (zvanična brew formula, rjeđe se ažurira)
sudo pacman -S opencode # Arch Linux (Stable)
paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # Bilo koji OS
nix run nixpkgs#opencode # ili github:anomalyco/opencode za najnoviji dev branch
```
> [!TIP]
> Ukloni verzije starije od 0.1.x prije instalacije.
### Desktop aplikacija (BETA)
OpenCode je dostupan i kao desktop aplikacija. Preuzmi je direktno sa [stranice izdanja](https://github.com/anomalyco/opencode/releases) ili sa [opencode.ai/download](https://opencode.ai/download).
| Platforma | Preuzimanje |
| --------------------- | ---------------------------------- |
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
| Windows | `opencode-desktop-windows-x64.exe` |
| Linux | `.deb`, `.rpm`, ili AppImage |
```bash
# macOS (Homebrew)
brew install --cask opencode-desktop
# Windows (Scoop)
scoop bucket add extras; scoop install extras/opencode-desktop
```
#### Instalacijski direktorij
Instalacijska skripta koristi sljedeći redoslijed prioriteta za putanju instalacije:
1. `$OPENCODE_INSTALL_DIR` - Prilagođeni instalacijski direktorij
2. `$XDG_BIN_DIR` - Putanja usklađena sa XDG Base Directory specifikacijom
3. `$HOME/bin` - Standardni korisnički bin direktorij (ako postoji ili se može kreirati)
4. `$HOME/.opencode/bin` - Podrazumijevana rezervna lokacija
```bash
# Primjeri
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
```
### Agenti
OpenCode uključuje dva ugrađena agenta između kojih možeš prebacivati tasterom `Tab`.
- **build** - Podrazumijevani agent sa punim pristupom za razvoj
- **plan** - Agent samo za čitanje za analizu i istraživanje koda
- Podrazumijevano zabranjuje izmjene datoteka
- Traži dozvolu prije pokretanja bash komandi
- Idealan za istraživanje nepoznatih codebase-ova ili planiranje izmjena
Uključen je i **general** pod-agent za složene pretrage i višekoračne zadatke.
Koristi se interno i može se pozvati pomoću `@general` u porukama.
Saznaj više o [agentima](https://opencode.ai/docs/agents).
### Dokumentacija
Za više informacija o konfiguraciji OpenCode-a, [**pogledaj dokumentaciju**](https://opencode.ai/docs).
### Doprinosi
Ako želiš doprinositi OpenCode-u, pročitaj [upute za doprinošenje](./CONTRIBUTING.md) prije slanja pull requesta.
### Gradnja na OpenCode-u
Ako radiš na projektu koji je povezan s OpenCode-om i koristi "opencode" kao dio naziva, npr. "opencode-dashboard" ili "opencode-mobile", dodaj napomenu u svoj README da projekat nije napravio OpenCode tim i da nije povezan s nama.
---
**Pridruži se našoj zajednici** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
-129
View File
@@ -1,129 +0,0 @@
<p align="center">
<a href="https://opencode.ai">
<picture>
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
</picture>
</a>
</p>
<p align="center">Den open source AI-kodeagent.</p>
<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
</p>
<p align="center">
<a href="README.md">English</a> |
<a href="README.zh.md">简体中文</a> |
<a href="README.zht.md">繁體中文</a> |
<a href="README.ko.md">한국어</a> |
<a href="README.de.md">Deutsch</a> |
<a href="README.es.md">Español</a> |
<a href="README.fr.md">Français</a> |
<a href="README.it.md">Italiano</a> |
<a href="README.da.md">Dansk</a> |
<a href="README.ja.md">日本語</a> |
<a href="README.pl.md">Polski</a> |
<a href="README.ru.md">Русский</a> |
<a href="README.bs.md">Bosanski</a> |
<a href="README.ar.md">العربية</a> |
<a href="README.no.md">Norsk</a> |
<a href="README.br.md">Português (Brasil)</a> |
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a> |
<a href="README.vi.md">Tiếng Việt</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
---
### Installation
```bash
# YOLO
curl -fsSL https://opencode.ai/install | bash
# Pakkehåndteringer
npm i -g opencode-ai@latest # eller bun/pnpm/yarn
scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS og Linux (anbefalet, altid up to date)
brew install opencode # macOS og Linux (officiel brew formula, opdateres sjældnere)
sudo pacman -S opencode # Arch Linux (Stable)
paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # alle OS
nix run nixpkgs#opencode # eller github:anomalyco/opencode for nyeste dev-branch
```
> [!TIP]
> Fjern versioner ældre end 0.1.x før installation.
### Desktop-app (BETA)
OpenCode findes også som desktop-app. Download direkte fra [releases-siden](https://github.com/anomalyco/opencode/releases) eller [opencode.ai/download](https://opencode.ai/download).
| Platform | Download |
| --------------------- | ---------------------------------- |
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
| Windows | `opencode-desktop-windows-x64.exe` |
| Linux | `.deb`, `.rpm`, eller AppImage |
```bash
# macOS (Homebrew)
brew install --cask opencode-desktop
# Windows (Scoop)
scoop bucket add extras; scoop install extras/opencode-desktop
```
#### Installationsmappe
Installationsscriptet bruger følgende prioriteringsrækkefølge for installationsstien:
1. `$OPENCODE_INSTALL_DIR` - Tilpasset installationsmappe
2. `$XDG_BIN_DIR` - Sti der følger XDG Base Directory Specification
3. `$HOME/bin` - Standard bruger-bin-mappe (hvis den findes eller kan oprettes)
4. `$HOME/.opencode/bin` - Standard fallback
```bash
# Eksempler
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
```
### Agents
OpenCode har to indbyggede agents, som du kan skifte mellem med `Tab`-tasten.
- **build** - Standard, agent med fuld adgang til udviklingsarbejde
- **plan** - Skrivebeskyttet agent til analyse og kodeudforskning
- Afviser filredigering som standard
- Spørger om tilladelse før bash-kommandoer
- Ideel til at udforske ukendte kodebaser eller planlægge ændringer
Derudover findes der en **general**-subagent til komplekse søgninger og flertrinsopgaver.
Den bruges internt og kan kaldes via `@general` i beskeder.
Læs mere om [agents](https://opencode.ai/docs/agents).
### Dokumentation
For mere info om konfiguration af OpenCode, [**se vores docs**](https://opencode.ai/docs).
### Bidrag
Hvis du vil bidrage til OpenCode, så læs vores [contributing docs](./CONTRIBUTING.md) før du sender en pull request.
### Bygget på OpenCode
Hvis du arbejder på et projekt der er relateret til OpenCode og bruger "opencode" som en del af navnet; f.eks. "opencode-dashboard" eller "opencode-mobile", så tilføj en note i din README, der tydeliggør at projektet ikke er bygget af OpenCode-teamet og ikke er tilknyttet os på nogen måde.
---
**Bliv en del af vores community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
-129
View File
@@ -1,129 +0,0 @@
<p align="center">
<a href="https://opencode.ai">
<picture>
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
</picture>
</a>
</p>
<p align="center">Der Open-Source KI-Coding-Agent.</p>
<p align="center">
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
</p>
<p align="center">
<a href="README.md">English</a> |
<a href="README.zh.md">简体中文</a> |
<a href="README.zht.md">繁體中文</a> |
<a href="README.ko.md">한국어</a> |
<a href="README.de.md">Deutsch</a> |
<a href="README.es.md">Español</a> |
<a href="README.fr.md">Français</a> |
<a href="README.it.md">Italiano</a> |
<a href="README.da.md">Dansk</a> |
<a href="README.ja.md">日本語</a> |
<a href="README.pl.md">Polski</a> |
<a href="README.ru.md">Русский</a> |
<a href="README.bs.md">Bosanski</a> |
<a href="README.ar.md">العربية</a> |
<a href="README.no.md">Norsk</a> |
<a href="README.br.md">Português (Brasil)</a> |
<a href="README.th.md">ไทย</a> |
<a href="README.tr.md">Türkçe</a> |
<a href="README.uk.md">Українська</a> |
<a href="README.bn.md">বাংলা</a> |
<a href="README.gr.md">Ελληνικά</a> |
<a href="README.vi.md">Tiếng Việt</a>
</p>
[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](https://opencode.ai)
---
### Installation
```bash
# YOLO
curl -fsSL https://opencode.ai/install | bash
# Paketmanager
npm i -g opencode-ai@latest # oder bun/pnpm/yarn
scoop install opencode # Windows
choco install opencode # Windows
brew install anomalyco/tap/opencode # macOS und Linux (empfohlen, immer aktuell)
brew install opencode # macOS und Linux (offizielle Brew-Formula, seltener aktualisiert)
sudo pacman -S opencode # Arch Linux (Stable)
paru -S opencode-bin # Arch Linux (Latest from AUR)
mise use -g opencode # jedes Betriebssystem
nix run nixpkgs#opencode # oder github:anomalyco/opencode für den neuesten dev-Branch
```
> [!TIP]
> Entferne Versionen älter als 0.1.x vor der Installation.
### Desktop-App (BETA)
OpenCode ist auch als Desktop-Anwendung verfügbar. Lade sie direkt von der [Releases-Seite](https://github.com/anomalyco/opencode/releases) oder [opencode.ai/download](https://opencode.ai/download) herunter.
| Plattform | Download |
| --------------------- | ---------------------------------- |
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
| Windows | `opencode-desktop-windows-x64.exe` |
| Linux | `.deb`, `.rpm` oder AppImage |
```bash
# macOS (Homebrew)
brew install --cask opencode-desktop
# Windows (Scoop)
scoop bucket add extras; scoop install extras/opencode-desktop
```
#### Installationsverzeichnis
Das Installationsskript beachtet die folgende Prioritätsreihenfolge für den Installationspfad:
1. `$OPENCODE_INSTALL_DIR` - Benutzerdefiniertes Installationsverzeichnis
2. `$XDG_BIN_DIR` - XDG Base Directory Specification-konformer Pfad
3. `$HOME/bin` - Standard-Binärverzeichnis des Users (falls vorhanden oder erstellbar)
4. `$HOME/.opencode/bin` - Standard-Fallback
```bash
# Beispiele
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
```
### Agents
OpenCode enthält zwei eingebaute Agents, zwischen denen du mit der `Tab`-Taste wechseln kannst.
- **build** - Standard-Agent mit vollem Zugriff für Entwicklungsarbeit
- **plan** - Nur-Lese-Agent für Analyse und Code-Exploration
- Verweigert Datei-Edits standardmäßig
- Fragt vor dem Ausführen von bash-Befehlen nach
- Ideal zum Erkunden unbekannter Codebases oder zum Planen von Änderungen
Außerdem ist ein **general**-Subagent für komplexe Suchen und mehrstufige Aufgaben enthalten.
Dieser wird intern genutzt und kann in Nachrichten mit `@general` aufgerufen werden.
Mehr dazu unter [Agents](https://opencode.ai/docs/agents).
### Dokumentation
Mehr Infos zur Konfiguration von OpenCode findest du in unseren [**Docs**](https://opencode.ai/docs).
### Beitragen
Wenn du zu OpenCode beitragen möchtest, lies bitte unsere [Contributing Docs](./CONTRIBUTING.md), bevor du einen Pull Request einreichst.
### Auf OpenCode aufbauen
Wenn du an einem Projekt arbeitest, das mit OpenCode zusammenhängt und "opencode" als Teil seines Namens verwendet (z.B. "opencode-dashboard" oder "opencode-mobile"), füge bitte einen Hinweis in deine README ein, dass es nicht vom OpenCode-Team gebaut wird und nicht in irgendeiner Weise mit uns verbunden ist.
---
**Tritt unserer Community bei** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)

Some files were not shown because too many files have changed in this diff Show More