Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b5bd866
docs: add implementation plan for resume-validation
danshapiro Jul 30, 2026
6d11a14
docs(resume-validation): harden plan per load-bearing validation find…
danshapiro Jul 30, 2026
fa0ca9a
docs(resume-validation): fix fresheyes round-1 blocking plan defects
danshapiro Jul 30, 2026
d025f4b
docs(resume-validation): wire sidecar liveness arm into door-3 gate p…
danshapiro Jul 30, 2026
bad363d
docs(resume-validation): fix fresheyes round-3 blocking plan defects
danshapiro Jul 30, 2026
944c528
feat(resume-validation): pure resume-gate policy in freshell-platform
danshapiro Jul 30, 2026
3e6c189
feat(resume-validation): read-only amplifier session_on_disk scan
danshapiro Jul 30, 2026
40f3050
refactor(resume-validation): split session_on_disk tests into sibling…
danshapiro Jul 30, 2026
bb788a4
feat(resume-validation): amplifier + codex by-id existence fallbacks
danshapiro Jul 30, 2026
f8d6ce4
feat(resume-validation): RetiredReason::SessionMissing + retire_missing
danshapiro Jul 30, 2026
31ea092
feat(resume-validation): optional notice on terminal.created, rendere…
danshapiro Jul 30, 2026
67367ab
feat(resume-validation): gate WS terminal.create restore path on disk…
danshapiro Jul 30, 2026
5452827
feat(resume-validation): gate headless auto-resume respawn on disk ex…
danshapiro Jul 30, 2026
644a9b4
feat(resume-validation): gate freshagent REST create pipeline on disk…
danshapiro Jul 30, 2026
54d3b37
docs(resume-validation): deviation ledger entry for spawn-door resume…
danshapiro Jul 30, 2026
6e7a7ec
fix(resume-validation): confine the ~1s codex rollout walk to gate ca…
danshapiro Jul 30, 2026
5c5c5b9
docs: add implementation plan for resume-validation reconciliation wi…
danshapiro Jul 30, 2026
53db931
docs(plans): harden reconciliation plan with load-bearing validation …
danshapiro Jul 30, 2026
943a03b
docs(plans): fix stale commit-count gates in reconciliation plan (19-…
danshapiro Jul 30, 2026
3218faa
docs(plans): make reconciliation commit-count gates self-stable (meas…
danshapiro Jul 30, 2026
bb873ee
rebase(resume-validation): reconcile with main @ 39010cb57 (S5 manage…
danshapiro Jul 30, 2026
335919c
fix(freshagent): run PIN 2 prealloc for gate-minted fresh claude ids
danshapiro Jul 30, 2026
ba012b8
test: pin FRESHELL_CODEX_MANAGED_LAUNCH=0 in resume-validation plain-…
danshapiro Jul 30, 2026
0257146
test(ws): pin gate-before-plan ordering under managed-default codex l…
danshapiro Jul 30, 2026
0287337
test(ws): assert gate-fired respawn keeps #582 breaker bookkeeping si…
danshapiro Jul 30, 2026
30a475c
docs: reconcile DEV-0010 with S5.e managed-launch default ON
danshapiro Jul 30, 2026
5af7e94
fix(resume-validation): add type alias for counting_on_stale_resume r…
danshapiro Jul 30, 2026
ac626db
docs: add implementation plan for resume-validation rebase onto main …
danshapiro Jul 30, 2026
4cc2525
docs: harden #589-rebase plan from load-bearing validation — case-7b …
danshapiro Jul 31, 2026
85150b6
docs: fix Task 4 probe snippet in #589-rebase plan — wrap AlwaysAbsen…
danshapiro Jul 31, 2026
7f5f1ee
docs: fix two blocking verification-gate defects in #589-rebase plan …
danshapiro Jul 31, 2026
a777d15
test(ws): first direct unit tests for derive_launch_prep — pin resume…
danshapiro Jul 31, 2026
dfeb67b
test(ws): pin server-allocated resume ids are never gated (claude + a…
danshapiro Jul 31, 2026
71e3633
fix(ws): run the codex wire-resume gate off-permit before planning — …
danshapiro Jul 31, 2026
53b77b5
docs: reconcile deviation ledger with off-permit codex gate placement…
danshapiro Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions crates/freshell-freshagent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,28 @@ pub use spawn_gate::{SpawnGate, SpawnGateError};
/// Defaults to "always false" (behavior-preserving for constructors that don't wire it).
pub type TerminalLivenessProbe = std::sync::Arc<dyn Fn(&str, &str) -> bool + Send + Sync>;

/// Door 3 (resume-validation): the gate-fired callback shape --
/// `(provider, stale_session_id)`. In production, `freshell-server`'s main.rs
/// implements it as pane-ledger `retire_missing` + `tracing::warn!`.
pub type OnStaleResume = std::sync::Arc<dyn Fn(&str, &str) + Send + Sync>;

/// Door 3 (resume-validation): the injected ASYNC sidecar-liveness probe --
/// `(mode, session_id) -> is that session live inside a fresh-agent sidecar`.
/// The REST create gate's registry consult is `TerminalRegistry`/PTY-scoped
/// and structurally BLIND to sessions live inside the fresh-agent sidecars
/// (sidecars never get PTY rows), so the in-gate liveness precondition needs
/// this second arm. Precedent: [`TerminalLivenessProbe`] solves the SAME
/// cross-crate liveness problem in the opposite direction -- same shape, made
/// async. Constructed by `freshell-server`'s `main.rs` over the SAME sidecar
/// instances the WS door's D7 join consults, so this crate never imports
/// `freshell-ws`. `None` (not wired, e.g. bare unit-test states) => the arm
/// contributes false.
pub type SidecarLivenessProbe = std::sync::Arc<
dyn Fn(&str, &str) -> std::pin::Pin<Box<dyn std::future::Future<Output = bool> + Send>>
+ Send
+ Sync,
>;

/// Handle pairing the shared gate with the permit-wait timeout
/// (`CreateProtectConfig.spawn_timeout_ms`, resolved once in main.rs so both
/// doors share one env snapshot).
Expand Down Expand Up @@ -224,6 +246,20 @@ pub struct FreshAgentState {
/// `identity_sink`). `None` = ungated (unwired unit tests keep legacy
/// behavior; production always wires it).
spawn_gate: Arc<std::sync::OnceLock<RestSpawnGate>>,
/// Door 3 (resume-validation): the disk-existence probe consulted before
/// a REST create turns a cached resume id into resume argv. Injected as
/// [`freshell_platform::resume_gate::ResumeProbeFn`] because this crate
/// must NOT depend on `freshell-ws` (whose probe trait would be circular
/// -- see this Cargo.toml's freshell-sessions comment). `None` = feature
/// off = today's behavior (every pre-existing test).
pub(crate) resume_probe: Option<freshell_platform::resume_gate::ResumeProbeFn>,
/// Door 3: called with `(provider, stale_session_id)` when the gate
/// fires. `freshell-server`'s main.rs implements it as pane-ledger
/// `retire_missing` + `tracing::warn!`. `None` = no-op.
pub(crate) on_stale_resume: Option<OnStaleResume>,
/// Door 3: arm 2 of the in-gate liveness precondition (see
/// [`SidecarLivenessProbe`]). `None` = arm contributes false.
pub(crate) sidecar_liveness: Option<SidecarLivenessProbe>,
}

/// A fresh-agent pane (the `paneContent` subset the opencode T2 path needs).
Expand Down Expand Up @@ -292,6 +328,9 @@ impl FreshAgentState {
codex_locator: None,
identity_sink: Arc::new(std::sync::OnceLock::new()),
spawn_gate: Arc::new(std::sync::OnceLock::new()),
resume_probe: None,
on_stale_resume: None,
sidecar_liveness: None,
}
}

Expand Down Expand Up @@ -480,6 +519,37 @@ impl FreshAgentState {
self
}

/// Door 3 (resume-validation): wire the disk-existence probe the REST
/// create pipeline consults before turning a cached resume id into
/// resume argv. `freshell-server`'s main.rs builds it over the SAME
/// `SessionExistenceProbe` the WS doors use. Unwired = feature off =
/// today's behavior.
pub fn with_resume_probe(
mut self,
probe: freshell_platform::resume_gate::ResumeProbeFn,
) -> Self {
self.resume_probe = Some(probe);
self
}

/// Door 3: wire the gate-fired callback (`(provider, stale_session_id)`),
/// implemented by `freshell-server`'s main.rs as pane-ledger
/// `retire_missing` + `tracing::warn!`.
pub fn with_on_stale_resume(mut self, cb: OnStaleResume) -> Self {
self.on_stale_resume = Some(cb);
self
}

/// Door 3: wire arm 2 of the in-gate liveness precondition (see
/// [`SidecarLivenessProbe`]). MUST stay a consuming `with_*` builder like
/// its siblings, NOT a `set_*`/`OnceLock` late-bind: `FreshOpencodeState`
/// holds a `FreshAgentState` clone by value, so a late-bound handle back
/// to the sidecars would create a real Arc cycle.
pub fn with_sidecar_liveness(mut self, probe: SidecarLivenessProbe) -> Self {
self.sidecar_liveness = Some(probe);
self
}

/// SESSION-09 fix-forward: replace this state's own `sessions_revision`
/// counter with a SHARED one -- in production, `freshell-server` wires
/// this to the SAME `Arc<AtomicI64>` as `freshell_ws::WsState::sessions_revision`
Expand Down
Loading
Loading