Repository navigation
Bump docker/build-push-action from 6 to 7 - #1
Closed
dependabot[bot] wants to merge 1 commit into
Closed
dependabot[bot] wants to merge 1 commit into
dependabot[bot] wants to merge 1 commit into
Conversation
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](docker/build-push-action@v6...v7) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Contributor
Author
LabelsThe following labels could not be found: Please fix the above issues or remove invalid values from |
Contributor
Author
|
OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting If you change your mind, just re-open this PR and I'll resolve any conflicts on it. |
dependabot
Bot
deleted the
dependabot/github_actions/docker/build-push-action-7
branch
March 12, 2026 01:59
This was referenced Mar 15, 2026
This was referenced Mar 17, 2026
houko
added a commit
that referenced
this pull request
Mar 18, 2026
…canning alert The help text contained a sample URI (user:password@cluster) that triggered GitHub secret scanning alert #1. Replace with angle-bracket placeholders so it is no longer flagged.
f-liva
referenced
this pull request
in f-liva/librefang
Mar 19, 2026
…canning alert The help text contained a sample URI (user:password@cluster) that triggered GitHub secret scanning alert #1. Replace with angle-bracket placeholders so it is no longer flagged.
This was referenced Mar 20, 2026
6 of 8 tasks
This was referenced Apr 13, 2026
houko
added a commit
that referenced
this pull request
Apr 14, 2026
Three real issues found while reviewing the original commit on this branch: 1. **`/api/status` still reachable.** The status handler at routes/config.rs:48 returns the full agents listing (id, name, state, model provider, profile) plus `home_dir`, `api_listen`, session count and memory usage — exactly the enumeration surface `require_auth_for_reads` exists to close. The original commit kept it in `always_public_method_free`, so flipping the flag did not actually stop `/api/status` from leaking. Moved it into `dashboard_read_exact` (still GET-only) so the flag locks it down. 2. **Flag silently no-ops when only user_api_keys or dashboard credentials are configured.** The gate was `require_auth_for_reads && api_key_present` where `api_key_present = !api_key.trim().is_empty()`. Production deployments commonly configure per-user keys or dashboard user/pass without a standalone api_key — in those cases, flipping the flag did nothing. Replaced with the full "any auth configured" predicate: `api_key || !user_api_keys.is_empty() || dashboard_auth_enabled`, which mirrors the existing auth-bypass check a few lines below. 3. **No operator feedback when flag is on but no auth exists.** If `require_auth_for_reads = true` is set without any auth configured, the flag is (correctly) a no-op at the middleware layer — but the operator gets no signal. Added a startup `warn!` in `build_router` so the misconfig is visible in logs at boot. Tests added: - `test_require_auth_for_reads_blocks_api_status` — locks in the fix for #1. - `test_require_auth_for_reads_engages_with_user_api_keys_only` — locks in the fix for #2 (both the 401 on missing creds and the 200 on a valid per-user key). - `test_require_auth_for_reads_is_noop_without_any_auth` — pins the middleware contract for #3 (the warning handles UX, middleware stays permissive when no auth backends exist). Verified: `cargo test -p librefang-api --lib middleware` — 20 passed (7 new for this feature) and `cargo clippy -p librefang-api -p librefang-types --all-targets -- -D warnings` clean.
2 of 3 tasks
houko
added a commit
that referenced
this pull request
Apr 14, 2026
…2398) * feat(api): add require_auth_for_reads flag to lock down dashboard reads The dashboard public-read allowlist in the auth middleware hard-codes /api/agents, /api/config, /api/budget, /api/sessions, /api/approvals, /api/hands, /api/skills, /api/workflows and more as GET-public so the SPA can render before the user enters credentials. With the default api_listen = "0.0.0.0:4545", any host on the LAN can enumerate agents, read configuration (minus redacted api_key), observe spend, and list pending approvals without a token — a hard mismatch with the "Bearer token authentication" line in SECURITY.md. Introduce KernelConfig.require_auth_for_reads (default false, so existing deployments keep rendering unchanged). When it is true AND api_key is configured, the middleware collapses the allowlist to the static-asset / OAuth / health subset and forces every dashboard read through bearer authentication. Unauthenticated health probes, OAuth callback, and dashboard shell HTML remain reachable. Refactor the public-path match into matches!()-based groups so clippy (nonminimal_bool) stays quiet and the two tiers are obviously separated in code review. Regression coverage: - require_auth_for_reads=true blocks unauthenticated GET /api/agents - require_auth_for_reads=true still allows the correct bearer - /api/health stays public with the flag on - require_auth_for_reads=false preserves the legacy public GET * fix(api): close require_auth_for_reads gaps found in self-review Three real issues found while reviewing the original commit on this branch: 1. **`/api/status` still reachable.** The status handler at routes/config.rs:48 returns the full agents listing (id, name, state, model provider, profile) plus `home_dir`, `api_listen`, session count and memory usage — exactly the enumeration surface `require_auth_for_reads` exists to close. The original commit kept it in `always_public_method_free`, so flipping the flag did not actually stop `/api/status` from leaking. Moved it into `dashboard_read_exact` (still GET-only) so the flag locks it down. 2. **Flag silently no-ops when only user_api_keys or dashboard credentials are configured.** The gate was `require_auth_for_reads && api_key_present` where `api_key_present = !api_key.trim().is_empty()`. Production deployments commonly configure per-user keys or dashboard user/pass without a standalone api_key — in those cases, flipping the flag did nothing. Replaced with the full "any auth configured" predicate: `api_key || !user_api_keys.is_empty() || dashboard_auth_enabled`, which mirrors the existing auth-bypass check a few lines below. 3. **No operator feedback when flag is on but no auth exists.** If `require_auth_for_reads = true` is set without any auth configured, the flag is (correctly) a no-op at the middleware layer — but the operator gets no signal. Added a startup `warn!` in `build_router` so the misconfig is visible in logs at boot. Tests added: - `test_require_auth_for_reads_blocks_api_status` — locks in the fix for #1. - `test_require_auth_for_reads_engages_with_user_api_keys_only` — locks in the fix for #2 (both the 401 on missing creds and the 200 on a valid per-user key). - `test_require_auth_for_reads_is_noop_without_any_auth` — pins the middleware contract for #3 (the warning handles UX, middleware stays permissive when no auth backends exist). Verified: `cargo test -p librefang-api --lib middleware` — 20 passed (7 new for this feature) and `cargo clippy -p librefang-api -p librefang-types --all-targets -- -D warnings` clean. * fix(api): also lock /api/health/detail behind require_auth_for_reads Follow-up finding from self-review. `/api/health/detail`'s own handler doc comment at routes/config.rs:317 says "requires auth", but the middleware allowlist had it in the always-public set. The handler returns operational data that should not be reachable from a cold probe: - `panic_count` / `restart_count` from the supervisor - `agent_count` - `embedding_provider` / `embedding_model` / `extraction_model` (infra leak) - `config_warnings` — the full output of `KernelConfig::validate()`, which can tell a remote attacker exactly what's wrong with the deployment - event-bus `dropped_events` count Moved to `dashboard_read_exact` so it gets locked down when the flag is on. `/api/health` stays public because its payload is genuinely minimal (`status`, `version`, two-item `checks` array) and load balancers / orchestrators need it for probing. Test added: `test_require_auth_for_reads_blocks_api_health_detail` — pins both contracts (/api/health stays public, /api/health/detail becomes auth-required) in a single test. Note: this is a partial fix. With the flag OFF, `/api/health/detail` stays public to preserve backwards compatibility, which means the handler's own "requires auth" doc comment is still being violated in the default configuration. Making it always-require-auth is a separate behavioural change that belongs in its own PR. Tests: `cargo test -p librefang-api --lib middleware` — 21 passed (8 for require_auth_for_reads, up from 7). * fix(api): close residual info leaks in unauthenticated endpoints Two more findings from self-review of the always-public set: 1. **`/api/auth/dashboard-check` echoed the configured dashboard username to anonymous callers.** The SPA uses this endpoint before the user has logged in (to pick the right login form), so the route is legitimately unauthenticated — but returning `"username": "<admin>"` handed an anonymous remote caller one half of the credential pair, enabling targeted credential stuffing against `/auth/dashboard-login`. The `mode` field is sufficient for the SPA to pick the right login form; the user already knows their own username. Now always returns an empty string. 2. **`/api/version` echoed the machine hostname.** Version endpoints conventionally expose build info, but the hostname is a per-machine identifier that lets a remote probe correlate a daemon to a specific deployment target. Dropped from the payload. Operators who need the hostname should read it from the daemon's shell environment. No tests referenced either field, `cargo test -p librefang-api --lib` passes with 262 tests (no behaviour change for the dashboard SPA beyond needing the user to type their username at login time, which is how every other dashboard already works). Residual pre-existing gap noted for follow-up: `/api/health/detail`'s own doc comment says it "requires auth" but the middleware kept it public until this PR's flag was added. With the flag off it's still public, honouring backwards compatibility for existing operator probes. Making it unconditionally auth-required is a separate behavioural change. * fix(api): make /api/health/detail always require auth, matching its doc The handler at routes/config.rs:317 documents itself as "Full health diagnostics (requires auth)" and `/api/health`'s doc explicitly says "Use GET /api/health/detail for full diagnostics (requires auth)". The middleware was the only thing still treating it as public — a pre-existing mismatch that this PR's first pass only partially closed by flag-gating. Remove it from both `always_public` and `dashboard_read_exact`. With the endpoint in neither public list, the middleware's default auth-required path handles it, so `/api/health/detail` now requires a bearer token regardless of `require_auth_for_reads`. The handler contract and the middleware contract finally agree. Breaking change for operators: if anyone was probing `/api/health/detail` without auth, they'll start getting 401. `/api/health` (minimal liveness) stays public for load balancers and orchestrators, so the standard deployment probe path is unaffected. Monitoring setups that want the detailed view should configure a bearer token — that was the original design intent. Test replaced: `test_api_health_detail_always_requires_auth` now pins both directions — `/api/health` stays public with the flag off, and `/api/health/detail` is 401 regardless of whether the flag is on or off. Tests: `cargo test -p librefang-api --lib middleware` — 21 passed.
6 tasks done
houko
pushed a commit
that referenced
this pull request
Apr 14, 2026
…2503) * feat(gateway): add lib/identity.js identity normalization module (ID-01) - Pure functional module: isLidJid, isGroupJid, normalizeDeviceScopedJid, extractE164, phoneToJid, resolvePeerId, deriveOwnerJids - No side effects on require, no hidden state, cache passed as param - resolvePeerId honors 5-step heuristic from CONTEXT §A Specifics - Unit tests: 41 assertions across 7 describe blocks * refactor(gateway): import lib/identity in index.js Add import of identity helpers next to existing lib/echo-tracker import. No call-site changes yet — subsequent commits migrate sites one at a time. * refactor(gateway): use deriveOwnerJids for OWNER_JIDS Replace inline Set(map(n => n.replace(/^\+/, '') + '@s.whatsapp.net')) with the module helper. Behavior-preserving. * refactor(gateway): use phoneToJid for outbound JID normalization Unify the 3 identical inline copies in sendMessage/sendImage/sendAudio (they were verbatim duplicates; diff confirmed identical). Group JIDs still passthrough, phones still coerced to @s.whatsapp.net form. * refactor(gateway): use resolvePeerId + extractE164 in main cascade (ID-01, ID-03) - Main sender-resolution cascade (~line 1171-1197) replaced with a single resolvePeerId() call that honors the 5-step heuristic from CONTEXT §A. - Phone derivation via extractE164() — also fixes latent bug where device-scoped incoming JIDs ('123:45@s.whatsapp.net') previously yielded malformed '+123:45' phone strings; now correctly yields '+123'. - console.warn for unresolved identity becomes structured JSON per ID-03: {event: 'identity_unresolved', jid, reason, lid_cache_size, confidence}. - Preserved side effects outside the pure function per §Concerns #1: senderPn cache write and CS-02 resolveLidProactively both still run BEFORE resolvePeerId — the function only resolves, it never writes. - Renamed local 'isLidJid' (shadowed module fn) to 'isLid' per §Concerns #2 — all downstream references (ownerLidJids check at line ~1211) updated to the alias. * refactor(gateway): use identity helpers in catchup path - isLidJid/isGroupJid module fns replace inline endsWith checks. - phoneToJid replaces inline '+'-strip + '@s.whatsapp.net' append. * refactor(gateway): use isGroupJid in getGroupParticipants guard Final inline endsWith('@g.us') site removed. Zero inline JID suffix manipulation remains in index.js. * test(gateway): equivalence + ID-03 log tests for identity refactor - 12 equivalence assertions comparing pre-refactor inline logic to lib/identity helpers across: isLid, isGroup, deriveOwnerJids, phoneToJid, resolvePeerId (5 fixture shapes: plain phone, LID+senderPn, LID+cache, LID+participant, LID unresolvable), extractE164 device-strip latent fix, normalizeDeviceScopedJid passthrough. - 1 assertion validating ID-03 identity_unresolved JSON log shape (event, jid, reason, lid_cache_size, confidence all present). - Test count: 129 -> 142. --------- Co-authored-by: Federico Liva <federico.liva@espero.it>
houko
added a commit
that referenced
this pull request
Apr 18, 2026
…ation Fifth Codex batch on #2704 — covers the skill-evolution subsystem (code the upstream PR #2694 shipped to main but that shows up in this PR's diff), plus the KV race on worker telemetry writes. librefang-skills (evolution.rs): - fuzzy_find_and_replace rejects empty old_string up front (#16). - update_skill / patch_skill re-verify skill_dir.exists() under the lock so concurrent delete_skill can't be resurrected (#3, #5). - delete_skill runs validate_name to reject path traversal (#12). - remove_supporting_file canonicalises + contains-checks the target (#6), matching write_supporting_file. - write_supporting_file scans content BEFORE writing (#7) so a rejected update doesn't destroy the pre-existing valid file. librefang-runtime (tool_runner.rs): - tool_skill_evolve_delete resolves the real installed skill's parent dir via registry.get() (#15) instead of unconditionally targeting the global skills_dir. librefang-types / librefang-kernel (approval policy): - Default require_approval list includes skill_evolve_* (#14). Updated serde `true`-shorthand + tests to match. librefang-kernel (kernel/mod.rs): - build_skill_summary sanitises the category key (#1) — tags are third-party data. - extract_json_from_llm_response tries every '{' instead of giving up after the first (#8). - Background skill review uses the agent's resolved driver and model for cost attribution (#13); falls back to defaults only when manifest resolution fails. worker (github-stats-worker/index.js): - Click counts sharded across 8 blobs, unioned on read (#39). - UI error reports sharded across 4 blobs plus legacy-key fallback for historical data (#41).
houko
added a commit
that referenced
this pull request
Apr 19, 2026
…nMode + config toggle Codex review on PR #2767 caught three real bugs my self-review missed — they're all in the kernel wrapper around agent_loop, not in agent_loop itself, which was the only surface I'd systematically audited last round. Fixes: 1. **Kernel persistence leakage into fork turns** (P1, codex #3106080994 + #3106228335). `send_message_streaming_with_sender_and_opts` runs three post-loop writes outside of agent_loop that had no `is_fork` guard: - `memory.append_canonical(...)` — would write fork messages to the agent's canonical session (cross-channel memory layer). - `memory.write_jsonl_mirror(...)` — persists the fork session to the agent's workspace `sessions/` directory. - `append_daily_memory_log(...)` — writes the fork's final response into the workspace's daily log. Plus two compaction hooks that mutate canonical session on disk: - Pre-loop `compact_agent_session_with_id(...)` when the session crosses the threshold. - Post-loop (background-spawned) compaction trigger. All five now gated on `!loop_opts.is_fork`. Metering, quota, and tool-call accounting intentionally still fire for forks — those tokens were really consumed and should charge the agent's budget. 2. **SessionMode::New bypass** (P2, codex #3106259704). `send_message_streaming_with_sender_and_opts` selects the effective session via `match manifest.session_mode { Persistent => canonical, New => SessionId::new() }`. For an agent configured with `session_mode = "new"`, forks were landing on a fresh empty `SessionId::new()` — which means the fork's request prefix didn't match the parent's and Anthropic's prompt cache missed. Fork mode now forces Persistent / canonical regardless of the manifest. 3. **Global prompt_caching toggle not respected in extractor fallback** (P2, codex #3106259706). `LlmMemoryExtractor`'s fallback `driver.complete()` path (used when no kernel handle is installed, or when the fork call fails) hardcoded `prompt_caching: true`, overriding the operator's `KernelConfig.prompt_caching` setting. Added `prompt_caching` field to the extractor; `init_proactive_memory_full_with_extractor` takes it and the kernel threads `cfg.prompt_caching` through. The fork path already inherited this correctly because it reads the agent's manifest metadata that the kernel derives from the same global. 4. **Experiment metrics polluted by fork turns** (caught while sweeping for related issues, not in the codex review). `record_experiment_request` was firing for forks, distorting A/B-test latency / success / cost averages with derivative-call data. Gated on `!loop_opts.is_fork`. Token/cost accounting still runs for forks (see #1). Also addresses codex #3106269431 (recursion): that one was about the commit before `fae5ed54`, which already fixed it. Comment is stale. Tests: auto_dream 25/25, kernel + workspace clippy `-D warnings` clean. Self-audit note: next time I claim "逻辑完整" I need to walk the full call chain from KernelHandle entry through agent_loop return, not just the agent_loop internals — the kernel wrapper has substantial persistence side effects that I was skipping.
houko
added a commit
that referenced
this pull request
Apr 19, 2026
…2767) * feat(forked-agent): derivative LLM calls reuse parent's prompt cache Add `kernel.run_forked_agent_streaming(agent_id, prompt, allowed_tools)` for derivative LLM calls that want to fire on top of an agent's current context without persisting the derivative's own messages back to the canonical session. auto-dream is migrated over as the first consumer; auto_memorize and future post-turn features can use the same API. Mechanism: - The fork runs through the same `run_agent_loop_streaming` path as a normal main turn, but with a new `LoopOptions` bundle: - `is_fork: true` skips every `save_session_async` call (12 sites across non-streaming / streaming, circuit-break / max-tokens / max-iterations / timeout paths) so derivative messages never touch the canonical session on disk, and - injects `"is_fork": true` into each `AgentLoopEnd` hook context data (8 sites) — auto-dream's own hook skips fork turns so a dream doesn't trigger a nested dream on its own completion. - `allowed_tools: Some(Vec<String>)` enforces a runtime allowlist at tool *execute* time inside `execute_single_tool_call`. The request schema is NOT pre-filtered — keeping it byte-identical to the parent turn is what lets Anthropic's prompt cache hit. Prompt- injected forks that try a non-allowed tool get a synthetic error result back and can't actually invoke anything — same defense-in- depth pattern libre-code uses via `createAutoMemCanUseTool`. - Anthropic driver's `cache_control` marker extended from system-only to ALSO cover the last tool block so the (system + tools) prefix caches as one unit. auto-dream migration: - `run_dream` now calls `kernel.run_forked_agent_streaming` instead of `send_message_streaming_with_sender_context_and_routing(channel = AUTO_DREAM_CHANNEL, ...)`. The `SenderContext` magic-channel dance is gone. - The kernel-side request-time filter that stripped non-memory tools when `sender.channel == AUTO_DREAM_CHANNEL` is removed — that job is now done by `LoopOptions::allowed_tools` at execute time, which both honours cache alignment and covers non-dream forks too. - `AutoDreamTurnEndHook::on_event` checks `ctx.data["is_fork"]` and bails on fork turns so a dream's own AgentLoopEnd doesn't try to schedule another dream. Tests: auto_dream 25 passed; Anthropic driver 3 new tests covering last-tool cache_control placement, caching-off path, and empty-tools edge case; runtime --lib 1034 passed (unrelated pre-existing media_understanding::transcode_empty_input_errors failure depends on ffmpeg env). Clippy workspace --all-targets clean. Docs: memory page (en + zh) "how a dream runs" updated to describe the fork-from-canonical-session model and execute-time tool allowlist. CHANGELOG entry added. * feat(forked-agent): extend Anthropic cache_control to messages + dashboard viz Two additions to the forked-agent infrastructure landed in the previous commit: 1. Anthropic driver's cache_control was stamped on system and the last tool; now also on the last content block of the last message. This lifts cache coverage from (system + tools) — ~10-30% of a typical multi-turn conversation — to (system + tools + entire message history), i.e. everything except the new trailing user/fork prompt. For forks this means parent's final assistant reply caches, so the dream's own pass hits on the full preceding conversation instead of just the schema prefix. Plain-string `ApiContent::Text` messages are upgraded to single-block `Blocks` form when marking the last message's block, because Anthropic only accepts `cache_control` on structured content (rejects it on shorthand strings). Upgrade is lossless — `{type: text, text: "..."}` is API-equivalent to the raw string. Two new driver tests: marker placement on last-block-of-last- message, and absent-when-caching-off (ensures we don't leak markers to non-Anthropic providers). 2. Dashboard Settings page now shows cache-hit rate + provider cost in the dream progress row. Reads `AutoDreamProgress.usage` (new field in the TS type, mirrors Rust `DreamUsage`) and derives hit% as `cache_read / (cache_read + cache_creation + input)`. Without this, the whole forkedAgent cost win would only be visible via audit-event digging — now it's at the surface, so operators can confirm the optimisation worked without leaving the dashboard. Two i18n strings added (en + zh) for the Cache label and the two `title` tooltips. Not done (deliberate): - auto_memorize migration to forkedAgent. Turns out this isn't a simple port — auto_memorize goes through ProactiveMemoryStore's own extractor (rule-based OR LLM via a separate code path in `proactive.rs`), which returns a structured `ExtractionResult` with already-parsed memories/relations/conflicts. Moving it to forkedAgent means replacing that entire extractor pipeline with a forked agent that uses memory_store as a tool — a redesign of the proactive-memory flow, not a 30-line swap. Worth doing but belongs in its own PR with proper API design. Tracked for follow-up. Tests: driver lib 262 passed. Kernel auto_dream 25 passed. Clippy workspace --all-targets --D-warnings clean. * perf(proactive-memory): enable prompt_caching on auto_memorize LLM calls `LlmMemoryExtractor`'s `extract_memories` and `decide_action` both use stable system prompts (`EXTRACTION_SYSTEM_PROMPT`, ~1KB and `DECISION_SYSTEM_PROMPT`) across every invocation. The user message varies — it's the specific conversation text or candidate memory — but the system block is identical. Flipping `prompt_caching: false → true` on both request constructions lets Anthropic cache the system block and subsequent calls within the 5-min TTL read cache instead of re-billing at full rate. Each system-prompt cache hit saves ~250 tokens × whatever the active agent's auto_memorize rate is (typically once per user turn). Non-Anthropic providers ignore the flag (OpenAI caches automatically, others no-op), so enabling it is safe cross-provider. Does NOT migrate auto_memorize to the forkedAgent pattern itself. Deliberate, with honest reasoning: LlmMemoryExtractor is a structured-output LLM call (returns `{memories: [...], relations: [...]}` JSON that gets parsed, followed by an ADD/UPDATE/NOOP decision step). forkedAgent gives you a tool-using agent loop — a different usage shape. Migrating auto_memorize properly means rewriting its extractor as a tool-using agent that invokes `memory_store` directly (libre-code's `extractMemories` shape), which throws out the current structured-output flow and the ADD/UPDATE/NOOP decision logic. That's a ~600-line redesign with its own test surface and deserves its own PR — not a shortcut appended to this one. This small change captures most of the realistic Anthropic savings auto_memorize could get without the redesign. Full migration tracked as a follow-up. * feat(proactive-memory): migrate auto_memorize extractor to forkedAgent Routes the LlmMemoryExtractor's LLM call through the kernel's `run_forked_agent_oneshot` trait method so the extractor's request shares the parent agent's (system + tools + messages) cache key. On Anthropic this means auto_memorize's per-turn extraction call hits the prompt cache for the full conversation prefix, not just its own system block. Shape: - `KernelHandle::run_forked_agent_oneshot(agent_id, prompt, allowed_tools)` — new trait method, default errors out. Real kernel impl spawns `run_forked_agent_streaming`, drains the stream, and returns the final text. `allowed_tools = Some(vec![])` keeps the fork single-turn (no tool calls) — the model returns JSON which the extractor parses as usual. - `MemoryExtractor::extract_memories_with_agent_id` — new trait method, default forwards to `extract_memories(messages)`. LlmMemoryExtractor overrides to call the fork path when it has a kernel handle installed. - `LlmMemoryExtractor::install_kernel_handle` — called from `LibreFangKernel::set_self_handle` once `Arc<Self>` exists (same place the auto-dream hook gets wired). Extractor holds the weak ref in a `RwLock<Option<Weak<dyn KernelHandle>>>` since it's late- bound by design. - `init_proactive_memory_full_with_extractor` — new init variant that returns the concrete `Arc<LlmMemoryExtractor>` alongside the store so the kernel can install its weak handle later. Extraction prompt change: the fork's user message embeds `EXTRACTION_SYSTEM_PROMPT` followed by the conversation text. The fork's system prompt stays the agent's own — we can't replace it without breaking cache alignment. Agents with moderately large system prompts (~2KB) break even; larger ones are net positive. Rule-based DefaultMemoryExtractor ignores agent_id via the trait's default method, so kernels without LLM extraction are unaffected. Fallback: when no kernel handle is installed (tests / rule-based extractor / fork call error) falls back to the standalone `driver.complete()` path which still has `prompt_caching = true` from the previous commit — system prompt still caches. Tests: auto_dream 25/25. Clippy clean. Live integration verification needs an Anthropic key to confirm cache hit rate on auto_memorize requests. * fix(forked-agent): gate post-turn side effects on !is_fork Logic review caught a recursion bug: fork turns' finalize_successful_end_turn was still calling auto_memorize, which in turn spawns run_forked_agent_oneshot — so a fork completing would trigger another fork, ad infinitum. The file lock would eventually save us by rejecting the new acquire, but we'd still waste tokens and log errors on every cascade. Gate five post-turn side effects on !ctx.opts.is_fork: 1. `auto_retrieve` in setup_recalled_memories — for forks, injecting memory fragments into the prompt would break cache alignment with the parent (byte-identical messages is the whole point). Also semantically wrong: a fork's context should exactly match parent's. 2. `remember_interaction_best_effort` after final response — forks are ephemeral; their conversation shouldn't leak into the long-term memory bank. 3. `context_engine.after_turn` — per-turn engine state (summary chains, token budgets) shouldn't be advanced by a derivative turn that doesn't count as a real user interaction. 4. `auto_memorize` — critical; this was the recursion source. Fork turns skip, and the fork's own completion therefore doesn't fire another auto_memorize. 5. Added `opts` field to `RecallSetupContext` so the pre-turn auto_retrieve gate has access to it; threaded through both streaming and non-streaming callers. Also added `is_fork = opts.is_fork` to the "agent loop completed" info log so the distinction is visible in traces. Without these gates the fix from the previous commit was still unsafe: the first fork would complete, then on its way out its finalize would call auto_memorize, which would spawn another fork, which would also complete-then-spawn. Lock acquire would reject at fork N+1 but the damage (token burn + log spam) would already be done. Tests: auto_dream 25/25, memory 114/2 passed. Clippy `-D warnings` clean workspace-wide. * fix(forked-agent): address codex review — kernel persistence + SessionMode + config toggle Codex review on PR #2767 caught three real bugs my self-review missed — they're all in the kernel wrapper around agent_loop, not in agent_loop itself, which was the only surface I'd systematically audited last round. Fixes: 1. **Kernel persistence leakage into fork turns** (P1, codex #3106080994 + #3106228335). `send_message_streaming_with_sender_and_opts` runs three post-loop writes outside of agent_loop that had no `is_fork` guard: - `memory.append_canonical(...)` — would write fork messages to the agent's canonical session (cross-channel memory layer). - `memory.write_jsonl_mirror(...)` — persists the fork session to the agent's workspace `sessions/` directory. - `append_daily_memory_log(...)` — writes the fork's final response into the workspace's daily log. Plus two compaction hooks that mutate canonical session on disk: - Pre-loop `compact_agent_session_with_id(...)` when the session crosses the threshold. - Post-loop (background-spawned) compaction trigger. All five now gated on `!loop_opts.is_fork`. Metering, quota, and tool-call accounting intentionally still fire for forks — those tokens were really consumed and should charge the agent's budget. 2. **SessionMode::New bypass** (P2, codex #3106259704). `send_message_streaming_with_sender_and_opts` selects the effective session via `match manifest.session_mode { Persistent => canonical, New => SessionId::new() }`. For an agent configured with `session_mode = "new"`, forks were landing on a fresh empty `SessionId::new()` — which means the fork's request prefix didn't match the parent's and Anthropic's prompt cache missed. Fork mode now forces Persistent / canonical regardless of the manifest. 3. **Global prompt_caching toggle not respected in extractor fallback** (P2, codex #3106259706). `LlmMemoryExtractor`'s fallback `driver.complete()` path (used when no kernel handle is installed, or when the fork call fails) hardcoded `prompt_caching: true`, overriding the operator's `KernelConfig.prompt_caching` setting. Added `prompt_caching` field to the extractor; `init_proactive_memory_full_with_extractor` takes it and the kernel threads `cfg.prompt_caching` through. The fork path already inherited this correctly because it reads the agent's manifest metadata that the kernel derives from the same global. 4. **Experiment metrics polluted by fork turns** (caught while sweeping for related issues, not in the codex review). `record_experiment_request` was firing for forks, distorting A/B-test latency / success / cost averages with derivative-call data. Gated on `!loop_opts.is_fork`. Token/cost accounting still runs for forks (see #1). Also addresses codex #3106269431 (recursion): that one was about the commit before `fae5ed54`, which already fixed it. Comment is stale. Tests: auto_dream 25/25, kernel + workspace clippy `-D warnings` clean. Self-audit note: next time I claim "逻辑完整" I need to walk the full call chain from KernelHandle entry through agent_loop return, not just the agent_loop internals — the kernel wrapper has substantial persistence side effects that I was skipping. * fix(forked-agent): stop fork from hijacking parent's injection channel + abort handle Two more process-local state bugs my review missed — caught by walking the full kernel wrapper call chain this time (prompted by justified "why are there still so many issues" feedback). 1. **Injection channel overwrite.** `setup_injection_channel(agent_id)` inserts into `injection_senders` and `injection_receivers` DashMaps keyed by agent id. Fork turns hit the same `agent_id` key, so the fork's setup would overwrite the parent turn's channel. External code trying to inject into the parent's loop during the fork window (e.g. user sends another message via a channel that triggers `injection_senders[agent_id].send(...)`) would land on the fork's about-to-be-dropped sender instead. Then fork's teardown removes the entry entirely, leaving the parent with no channel for the rest of its turn. Fix: fork turns skip `setup_injection_channel` (pass `None` into agent_loop's `pending_messages`) and skip `teardown_injection_channel` on exit. 2. **Abort handle hijack.** `running_tasks.insert(agent_id, handle)` registers the current spawn for `stop_agent_run` / `suspend_agent` to find. Fork inserting under `agent_id` overwrites the parent's handle — so an operator calling `stop_agent_run(agent_id)` during the fork window aborts the fork instead of the user's actual turn. Worse, the parent's handle is gone from the map, so subsequent stop attempts return `Ok(false)` (not running) even though the parent is in fact mid-await. Fix: fork turns don't register in `running_tasks`. The fork's caller (auto_memorize, dream) already holds the join handle directly and can cancel via task-level abort if needed. This is a pattern that my first review should have caught but didn't — shared-by-agent-id state in DashMaps gets silently clobbered when the fork shares the agent_id. Both fixes gate on `is_fork`, same as the earlier persistence fixes. Tests: auto_dream 25/25. Clippy clean workspace-wide. Still outstanding: live integration testing (needs real Anthropic key to validate cache_read_input_tokens > 0 on fork calls). Everything static-verifiable now audited.
DaBlitzStein
added a commit
to DaBlitzStein/librefang
that referenced
this pull request
Aug 10, 2026
…ping, spawn depth CRITICAL librefang#1: insert RunHandle BEFORE tokio::spawn so self-cleanup remove_if always finds its entry. Prevents stale ghost entries. CRITICAL librefang#2: TUI create form now renders typed text (was _value, now value). Shows typed input in chunk[5], toggle status in step 3. HIGH librefang#1: agent_spawn now guarded by AGENT_CALL_DEPTH (mirrors agent_send depth guard). Prevents unbounded recursive spawn chains. Also: added agent_spawn to ALWAYS_NATIVE_TOOLS so all agents have it by default (was missing — agents only had agent_send). PRD: GOAL_SYSTEM_AUDIT_PRD.md documents 28 findings (2 critical, 1 high, 18 medium, 7 low) for future work. 14/14 tests pass. 3 deploys healthy.
DaBlitzStein
added a commit
to DaBlitzStein/librefang
that referenced
this pull request
Aug 11, 2026
…ping, spawn depth CRITICAL librefang#1: insert RunHandle BEFORE tokio::spawn so self-cleanup remove_if always finds its entry. Prevents stale ghost entries. CRITICAL librefang#2: TUI create form now renders typed text (was _value, now value). Shows typed input in chunk[5], toggle status in step 3. HIGH librefang#1: agent_spawn now guarded by AGENT_CALL_DEPTH (mirrors agent_send depth guard). Prevents unbounded recursive spawn chains. Also: added agent_spawn to ALWAYS_NATIVE_TOOLS so all agents have it by default (was missing — agents only had agent_send). PRD: GOAL_SYSTEM_AUDIT_PRD.md documents 28 findings (2 critical, 1 high, 18 medium, 7 low) for future work. 14/14 tests pass. 3 deploys healthy.
DaBlitzStein
added a commit
to DaBlitzStein/librefang
that referenced
this pull request
Aug 13, 2026
…ping, spawn depth CRITICAL librefang#1: insert RunHandle BEFORE tokio::spawn so self-cleanup remove_if always finds its entry. Prevents stale ghost entries. CRITICAL librefang#2: TUI create form now renders typed text (was _value, now value). Shows typed input in chunk[5], toggle status in step 3. HIGH librefang#1: agent_spawn now guarded by AGENT_CALL_DEPTH (mirrors agent_send depth guard). Prevents unbounded recursive spawn chains. Also: added agent_spawn to ALWAYS_NATIVE_TOOLS so all agents have it by default (was missing — agents only had agent_send). PRD: GOAL_SYSTEM_AUDIT_PRD.md documents 28 findings (2 critical, 1 high, 18 medium, 7 low) for future work. 14/14 tests pass. 3 deploys healthy.
DaBlitzStein
added a commit
to DaBlitzStein/librefang
that referenced
this pull request
Aug 14, 2026
…ping, spawn depth CRITICAL librefang#1: insert RunHandle BEFORE tokio::spawn so self-cleanup remove_if always finds its entry. Prevents stale ghost entries. CRITICAL librefang#2: TUI create form now renders typed text (was _value, now value). Shows typed input in chunk[5], toggle status in step 3. HIGH librefang#1: agent_spawn now guarded by AGENT_CALL_DEPTH (mirrors agent_send depth guard). Prevents unbounded recursive spawn chains. Also: added agent_spawn to ALWAYS_NATIVE_TOOLS so all agents have it by default (was missing — agents only had agent_send). PRD: GOAL_SYSTEM_AUDIT_PRD.md documents 28 findings (2 critical, 1 high, 18 medium, 7 low) for future work. 14/14 tests pass. 3 deploys healthy.
DaBlitzStein
added a commit
to DaBlitzStein/librefang
that referenced
this pull request
Aug 14, 2026
…ping, spawn depth CRITICAL librefang#1: insert RunHandle BEFORE tokio::spawn so self-cleanup remove_if always finds its entry. Prevents stale ghost entries. CRITICAL librefang#2: TUI create form now renders typed text (was _value, now value). Shows typed input in chunk[5], toggle status in step 3. HIGH librefang#1: agent_spawn now guarded by AGENT_CALL_DEPTH (mirrors agent_send depth guard). Prevents unbounded recursive spawn chains. Also: added agent_spawn to ALWAYS_NATIVE_TOOLS so all agents have it by default (was missing — agents only had agent_send). PRD: GOAL_SYSTEM_AUDIT_PRD.md documents 28 findings (2 critical, 1 high, 18 medium, 7 low) for future work. 14/14 tests pass. 3 deploys healthy.
This was referenced Aug 31, 2026
Merged
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps docker/build-push-action from 6 to 7.
Release notes
Sourced from docker/build-push-action's releases.
... (truncated)
Commits
d08e5c3Merge pull request #1479 from docker/dependabot/npm_and_yarn/docker/actions-t...cbd2dffchore: update generated contentf76f51fchore(deps): Bump@docker/actions-toolkitfrom 0.78.0 to 0.79.07d03e66Merge pull request #1473 from crazy-max/rm-deprecated-envs98f853dchore: update generated contentcadccf6remove deprecated envs03fe877Merge pull request #1478 from docker/dependabot/github_actions/docker/setup-b...827e366chore(deps): Bump docker/setup-buildx-action from 3 to 4e25db87Merge pull request #1474 from crazy-max/rm-export-build-tool1ac2573Merge pull request #1470 from crazy-max/node24Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)