Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
5f72a96
feat(sdk): stage connection-owned images across both session hosts
snowykr Oct 3, 2026
cac49fe
test(sdk): isolate terminal cases and exact-run settlement proof
snowykr Oct 3, 2026
701e46c
fix(sdk): enforce elapsed upload expiry before cleanup callbacks
snowykr Oct 3, 2026
17602a3
fix(sdk): retain merged generic retirement with image resource cleanup
snowykr Oct 4, 2026
8b3c396
test(session): publish complete writer readiness metadata atomically
snowykr Oct 4, 2026
fc4e531
test(sdk): synchronize generation-202 inventory and authority fixtures
snowykr Oct 4, 2026
11d519d
merge(session): retain SDK image layer on owned native base
snowykr Oct 4, 2026
4c20f1e
merge(storage): retain SDK images with authenticated logical owners
snowykr Oct 4, 2026
e30ef57
merge(sdk): retain current fenced retirement and retry correlation
snowykr Oct 4, 2026
fd82b07
docs(bench): cite the actual compile-argument source
snowykr Oct 4, 2026
9adf1e8
fix(session): authenticate existing scopes before cold cleanup replay
snowykr Oct 4, 2026
40788fb
fix(session): recertify exact stale cleanup completion summaries
snowykr Oct 4, 2026
093a987
docs(sdk): include independently verified recovery corrections
snowykr Oct 4, 2026
ea5c993
fix(session): dispose recertification capabilities on every exit
snowykr Oct 5, 2026
e5f695c
merge(sdk): preserve current immutable retirement and exact recovery …
snowykr Oct 5, 2026
25489e8
fix(integration): preserve current Codex replay vetoes in SDK owner
snowykr Oct 5, 2026
bbed72d
fix(sdk): carry independently verified owner wire projection
snowykr Oct 5, 2026
7c84e54
merge(stack): retain durable task owners in the SDK image owner
snowykr Oct 5, 2026
8b3b366
fix(sdk): retain strict existing-transcript owner publication
snowykr Oct 5, 2026
1eae592
fix(stack): remove owner guards superseded by maintainer withdrawal
snowykr Oct 5, 2026
85603c1
merge(stack): retain current owner rollback and exact native guards i…
snowykr Oct 5, 2026
fc684e1
test(session): preserve fork artifact IDs and independent writes acro…
snowykr Oct 5, 2026
6345120
merge(sdk): update owning layer to verified deletion successor
snowykr Oct 5, 2026
df33644
test(task): isolate failed-allocation regression model dependencies
snowykr Oct 5, 2026
b3f179d
test(task): own isolated parent catalogs across artifact fixtures
snowykr Oct 5, 2026
eae0ed5
fix(sdk): propagate independently verified terminal-owner correction
snowykr Oct 5, 2026
252a8aa
merge(sdk): retain verified pinned successor and descriptor-refusal f…
snowykr Oct 5, 2026
db1b7f9
fix(sdk): carry independently derived scoped terminal admission
snowykr Oct 5, 2026
483003b
test(sdk): carry independent pre-barrier startup queue proof
snowykr Oct 5, 2026
ffed105
fix(sdk): retain image ownership through durable queued teardown
snowykr Oct 5, 2026
f1d6329
test(sdk): propagate the combined daemon generation fixture
snowykr Oct 5, 2026
73cdf58
test(sdk): retain rejecting foreign-root seam and original wiring budget
snowykr Oct 5, 2026
bf123be
docs(sdk): retain independent queued-image and requester release cont…
snowykr Oct 5, 2026
bd71fb4
test(sdk): carry independent combined image teardown fault proof
snowykr Oct 5, 2026
ffba6ff
fix(sdk): propagate independently reviewed pure image limits
snowykr Oct 6, 2026
4296ce7
fix(session): keep public terminal settlement independent of extensions
snowykr Oct 6, 2026
e6d203a
feat(acp): stage inline prompt images through authenticated SDK uploads
snowykr Oct 3, 2026
07a4499
fix(acp): retain cancelled echo barriers across session replacement
snowykr Oct 3, 2026
4f37ecd
docs(sdk): retain identical independent cancellation guidance
snowykr Oct 4, 2026
c0e39c6
test(acp): assert local provider-preflight cancellation has no remote…
snowykr Oct 4, 2026
0cb96cb
test(acp): verify dispatch observer before uncertain reply recovery
snowykr Oct 4, 2026
fc2b6f5
docs(sdk): retain merged generic guarantees in the complete ACP layer
snowykr Oct 4, 2026
c365e5a
test(acp): retain acknowledged cancel while awaiting real terminal
snowykr Oct 4, 2026
20b9fb4
docs(acp): preserve large-inline and exact watchdog release contracts
snowykr Oct 5, 2026
6ba736b
docs(acp): retain failed publication lifetime in split release note
snowykr Oct 6, 2026
fe780d9
test(acp): join held upload response before observing cleanup
snowykr Oct 6, 2026
9d2e9f9
fix(acp): consume pure image limits without session tooling imports
snowykr Oct 6, 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
23 changes: 23 additions & 0 deletions crates/gjc-sdk/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ pub fn endpoint_path(state_root: &Path, session_id: &str) -> PathBuf {
/// Propagates filesystem errors (permissions, disk, etc.).
pub fn write_endpoint(state_root: &Path, record: &EndpointRecord) -> std::io::Result<PathBuf> {
let dir = endpoint_dir(state_root);
#[cfg(unix)]
{
use std::os::unix::fs::DirBuilderExt;
fs::DirBuilder::new()
.recursive(true)
.mode(0o700)
.create(&dir)?;
}
#[cfg(not(unix))]
fs::create_dir_all(&dir)?;
harden_dir(&dir)?;

Expand Down Expand Up @@ -371,6 +380,20 @@ mod tests {
fs::remove_dir_all(&root).ok();
}

#[cfg(unix)]
#[test]
fn newly_created_endpoint_parents_are_private() {
use std::os::unix::fs::PermissionsExt;
let root = temp_root();
let state_root = root.join(".gjc").join("state");
let rec = EndpointRecord::new("sess-1", "127.0.0.1", 5555, "tok");
write_endpoint(&state_root, &rec).unwrap();
for dir in [root.join(".gjc"), state_root.clone(), endpoint_dir(&state_root)] {
assert_eq!(fs::metadata(dir).unwrap().permissions().mode() & 0o777, 0o700);
}
fs::remove_dir_all(root).unwrap();
}

#[cfg(unix)]
#[test]
fn file_is_private_0600() {
Expand Down
58 changes: 56 additions & 2 deletions docs/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,54 @@ activity, or an earlier pending claim.

Reconciliation state survives client disconnect/reconnect. With the session-private durable store (`.sdk-reconciliation/`), accepted and terminal prompt records also survive **GJC session-process restart** for the same session identity within capacity, subject to crash-consistent fsync. An ordinary non-terminal prompt record at restart finalizes its pending outcome and receipt state. A prompt with the explicit `deadlineRecoveryPending` marker is the exception: it remains `accepted` or `in_flight`, and its staged pending outcome is not exposed by Q26 while the SDK retains a durable recovery owner. A process restart does not recreate a missing exact-run/tool observation, so the pending outcome stays private until a real terminal event or new settlement evidence arrives. If ownership or settlement remains uncertain, the record stays nonterminal and recoverable instead of being converted into a synthetic deadline failure. A stopped prompt without receipt evidence becomes `terminal_ok + missing`; failed prompt or skill settlement without body evidence becomes `unknown`. Eviction or absence still returns honest `unknown`; that means the prior outcome is unknowable, not that execution did not occur. Active records are capped at 128 per kind and are never aged into terminal. Terminal records are capped at 256 per kind and evicted oldest-terminal first, with no age-based eviction. Reconciliation stores no prompt, transcript, credential, or provider-response body.

### ACP inline images and internal staging

ACP clients can submit standard inline `ContentBlock.image` data on both initial
and follow-up prompts. GJC preserves the encoded image's original bytes and MIME
type; clients do not need a custom upload API or a `resource_link`. The internal
SDK frame cap remains 256 KiB. Large inline images cross it through SDK-core-owned
`turn.image.begin`, `turn.image.append`, `turn.image.finish`, and
`turn.image.discard` controls, not by increasing the cap or exposing endpoint
credentials to the ACP client.

Staging is scoped to the authenticated live connection and session. Each upload
has a two-minute inactivity lease, a declared byte length, MIME type, and SHA-256 digest.
`turn.image.begin` optionally accepts a nonempty `batchId` of at most 128 characters.
Successful begin, append, and finish operations renew live uploads only for the same
authenticated connection and explicit batch; omitted batch IDs renew that upload alone.
ACP uses its request `clientRef` as the batch label so an early completed image stays
available while later images are still transferring. Invalid or rejected operations,
unrelated batches/connections, and cleanup do not renew these leases. Two minutes
without successful staging progress still retires the batch, without changing quotas.
Chunks are canonical base64 in sequence, with at most 96 KiB decoded per chunk.
The host verifies exact length, digest, MIME/header agreement, dimensions, and
image decoding before a reference is usable. A prompt accepts at most 16 images,
each at most 20 MiB, with 64 MiB source limits for pending and accepted images and
a shared 256 MiB process payload/copy budget. Appends coalesce into retained
96 KiB slabs instead of retaining one Buffer per fragment; tiny uploads reserve
at least 4 KiB. Session and process staging budgets charge the actual retained
allocation before it is created, and successful finalization releases slab
padding. Flexible fragment sizes remain supported without a chunk-count limit.
Expiry, discard, and connection loss retire unconsumed upload capacity.

Finished references are connection-owned and one-shot. `turn.prompt` consumes
`stagedImages: [{ id }]`; callers cannot mix them with direct `images` or reuse
references after redemption, including a subsequent admission rejection. ACP's
bounded retry after a **confirmed** `busy` response restages the original bytes
with fresh references. Capacity rejection may precede redemption, so old IDs
must be discarded or confirmed already gone before fresh staging. Unconfirmed
cleanup emits a bounded diagnostic and refuses the retry; it does not invent
capacity release or replay an uncertain mutation. A lost or uncertain acknowledgement is reconciled through
`turn.result`, never treated as permission to upload and execute the prompt again.
The user's message is echoed once across a confirmed retry. Upload validation and
final staged-envelope bounds pass before any text/image echo is published, so an
upload rejection cannot leave an accepted-looking transcript entry. Validated replies
for this request's active staging renew only its local ACP inactivity watchdog; they
are not model/tool activity or execution authority. This pre-dispatch boundary does
not suppress the user message for later prompt-admission or admitted model failures.

Cancellation applies to the exact outstanding prompt, including successor
admission while a cancelled image's user-message echo is still being published.
### Request-owned queue cancellation and execution deadlines

SDK-only ordinary abort cancels a snapshot of its authenticated requester's
Expand Down Expand Up @@ -449,6 +497,9 @@ progress in the same consuming run and cancellation domain. Session teardown
retires joined attribution; late predecessor progress or terminal events cannot
adopt or settle a successor. Transport or delivery failure alone does not prove
execution settled and does not retire a live unsettled execution owner.
Accepted-image quota shares that exact run/domain ownership: transport diagnostics
or delivery-record expiry cannot release it. Only exact run terminal or session
teardown releases retained image capacity.

`turn.prompt` remains ordered and non-idempotent. Its envelope `idempotencyKey`
does not replay a response or produce `idempotency_conflict`. A retained duplicate
Expand All @@ -471,8 +522,11 @@ accepted turn count — including a running tool's partial-result `tool_executio
long-running tool that streams output (e.g. a multi-minute compile) keeps renewing the lease mid-run;
heartbeats, streaming text/thinking deltas, retries, other turns/sessions, and
unrelated session noise do not renew the lease, and out-of-order delivery never shortens it. The
hard maximum is never unbounded: every renewal is capped at `acceptedAt + sdk.promptMaxRuntimeMs` so a
wedged or continuously noisy prompt still reaches a deterministic terminal outcome. Terminalization then has a fixed `10_000` ms
hard maximum is never unbounded: every renewal is capped at the lease's start time plus
`sdk.promptMaxRuntimeMs`. The lease begins at acceptance for a directly executing prompt;
for confirmed queued input it is suspended during queue residence and begins again at
actual consumption or own-run promotion, without changing the durable `acceptedAt`.
A wedged or continuously noisy executing prompt still reaches a deterministic terminal outcome. Terminalization then has a fixed `10_000` ms
grace period, which is not configurable. A controlled terminal failure reaches ACP
as JSON-RPC `-32603` with `data.code` of `prompt_failed` or
`prompt_deadline_exceeded`.
Expand Down
7 changes: 7 additions & 0 deletions packages/coding-agent/changelog.d/5879-stack-acp-images.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Added
- Stage ACP inline prompt images larger than the private SDK frame limit through the authenticated SDK upload API without requiring Paseo-side uploads, while preserving original bytes and MIME and retaining the private 256 KiB full-frame limit.

### Fixed
- Preserve cancelled user-image publication barriers across same-session-ID reattachment, including bounded failures during recordless recovery, retaining failed publication until explicit retirement; waiting successors remain locally cancellable without host aborts.
- Validate image staging and the final prompt envelope before publishing the user echo, and renew the local watchdog only for this request's validated staging progress; this guarantee does not cover later prompt admission or model failure.
- Keep upload progress, cancellation, socket handoff and echo-tail fences request-owned. Retry busy admission with fresh upload IDs only after confirmed retirement, and reconcile uncertain acknowledgements through the original client reference without mutation replay.
17 changes: 17 additions & 0 deletions packages/coding-agent/changelog.d/5879-stack-sdk-images.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### Added

- Stage images on both standalone and notification SDK hosts using authenticated, one-shot upload references while preserving original bytes and MIME under the existing 256 KiB frame cap.
- Enforce pending, accepted and shared decode/copy allocation budgets; retain accepted capacity until exact consuming-run settlement or teardown, and prevent closed connection controls from recreating resources.
- Expire uploads after two minutes without successful staging progress; renew only live entries owned by the same authenticated connection and explicit batch, never rejected traffic or unrelated work. Enforce elapsed expiry even when cleanup callbacks are delayed, including redemption and in-flight finalization.

### Fixed

- Remove implicitly diverted queued images in both SDK hosts before acknowledging cancellation, and finalize each accepted image prompt at its exact consuming-run terminal or durably confirmed queue removal.
- Suspend image-prompt deadlines while queued, renew joined prompts only on their exact consuming run's progress, and preserve uncertainty when cancellation terminal persistence fails.
- Bound retained upload allocations for tiny image fragments as well as payload bytes.
- Cancel the SDK-only ordinary abort requester's admitted preflight snapshot through durable acceptance and before execution starts, without cancelling foreign or later admissions or inventing a durable terminal.
- Authenticate existing managed scope bindings and retained filesystem identities before cold cleanup recovery; never initialize or repair missing storage as a recovery shortcut.
- Re-certify a stale cleanup-completion digest only after independently verified completion through an exact descriptor-backed replacement; refuse destination swaps and replacement failures without replaying transcript deletion.
- Omit explicitly cleared optional owner fields from SDK cleanup replay decoding, matching their persisted JSON shape while retaining strict validation of present owner evidence and refusing replaced scope authority.
- Retain the originating SDK owner across todo and retry continuations, defer attempt-local failure diagnostics until the actual terminal, and keep real submission settlement behind its exact final publication; preserve acknowledged backoff cancellation without replaying a terminal or clearing committed failures, and keep resolving or rejected cleanup behind the actual durable-terminal recovery owner.
- Publish promoted SDK terminals after their captured handler and same-owner continuation decisions, and hold independent FIFO delivery behind that genuine public boundary without blocking the owning retry or todo continuation.
Loading
Loading