Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 8 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -859,8 +859,14 @@ native Run; they do not request model credentials or mutate the workspace. Cance
retains the existing Turn/reservation semantics. Do not introduce a second queue.
Publish metadata in the same transaction as Turn completion. Failed/cancelled Turns
discard private objects, and Session deletion removes both private and published
copies. Reuse the source-file snapshot reader pattern and common content response;
artifact deletion does not alter workspace files. Hosted execution requires the
copies. The exporter skips output symlinks by their `lstat` type without following,
opening or resolving them; hard links, other special files, device crossings and
concurrent changes still reject the capture. In that completion transaction, drop
staged paths whose sha256 equals the newest remaining published Artifact for the
path in the Session, so later Turns publish only new, changed or no-longer-published
paths and never modify existing Artifacts. Reuse the source-file snapshot reader
pattern and common content response; artifact deletion does not alter workspace
files. Hosted execution requires the
Runtime's bounded output-export capability and exact read-only preparation binding;
capability advertisement alone does not qualify an operator's deployment.
Exporter component checks do not establish public Artifact compatibility.
Expand Down
2 changes: 1 addition & 1 deletion contracts/agents-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ paths start at `/vaults`, not `/agents/vaults`.
| sessions.events | create, stream | Text/cancel/function-result admission and live events; function-action state snapshots supported |
| sessions.turns | retrieve, list | Implemented reads; lifecycle conformance still partial |
| sessions.items | list | Partial Item variants |
| sessions.artifacts | retrieve, list, delete, content | Shared output capture and immutable stored reads/deletion on accepted Docker profiles and [qualified user-managed workflows](user-managed-runtime-v1.md) (prior Core-managed E2B evidence remains historical), including retained downloads after Runtime loss; exact upstream defaults/errors, unchanged-file republishing and cancellation-edge parity remain unverified |
| sessions.artifacts | retrieve, list, delete, content | Shared output capture and immutable stored reads/deletion on accepted Docker profiles and [qualified user-managed workflows](user-managed-runtime-v1.md) (prior Core-managed E2B evidence remains historical), including retained downloads after Runtime loss. [Aligned](official-semantics-alignment.md#artifact-capture-and-listing--september-23) output symlink skipping, unchanged-path non-republication, the list envelope and malformed filters; exact upstream defaults/errors, hard-link/special-file capture and cancellation-edge parity remain unverified |
| sessions.subagents | retrieve, list | [Three-harness Docker reads, native lifecycle limits and real evidence](subagents.md); full multi-agent semantics remain partial |
| sessions.subagents.items | list | Qualified own-child history reads; full Item variants and live child streaming remain partial |
| sessions.subagents.turns | retrieve, list | Implemented; shared Session/child IDs |
Expand Down
72 changes: 72 additions & 0 deletions contracts/agents-api/official-semantics-alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,75 @@ invalid bodies and queries, and replay U+0000 on every create/update family with
a database digest proving no writes. The pinned-SDK acceptance scripts assert the
new codes, params and messages. Independent real-Core acceptance is recorded
separately by the coordinator.

## Artifact capture and listing — September 23

This batch aligns Session Artifact capture and listing with the first official
Artifact observations. Evidence comes from the hosted-environment campaign scan
recorded privately in `~/.parsar/remediation/20260923/campaign-scan-2/hosted-env/`
(`findings.json` HE-50..62, raw records under `official/` and `run1/`). The probe
used three owned Sessions and two tiny `gpt-6-astra` Turns; all three Sessions
were deleted. Official Turn 1 created regular, nested and empty outputs plus
`outputs/link.txt -> a.txt`; Turn 2 only wrote `outputs/c.txt` after one Artifact
was deleted.

| Row | Case | Core behavior |
| --- | --- | --- |
| A1 | A symlink below `outputs/` at Turn completion: to a file or directory, dangling, or pointing outside the workspace (HE-51) | Skipped by its `lstat` type: never followed, opened or resolved, and no Artifact. Every regular file is still captured and the Turn completes. |
| A2 | Later Turns in the same Session (HE-52) | A path is published again only when it has no remaining published Artifact in the Session, or its bytes (sha256) differ from the newest remaining one. Unchanged paths keep their existing Artifact IDs. The first Turn is unchanged. |
| A3 | List envelope (HE-53) | `object: list`, `data`, `first_id`, `last_id`, `has_more`, with null first/last IDs on an empty page, like the Session, Turn and Item lists. Paging and cursors are unchanged. |
| A4 | Malformed `environment_id` filter (HE-56) | 200 with an empty page, as for another existing Environment. Session lookup still runs first, so foreign and missing Sessions remain 404; cursor and limit errors are unchanged. |

Decisions:

- The Rust export helper handles every link kind the same way. Official evidence
shows one relative link to a file; telling the other kinds apart would require
resolving the link, which the confinement rules forbid. A link still counts as
a directory entry, so creating or removing one during export is a concurrent
change.
- The republication decision runs in the Turn's terminal transaction, not in the
private capture transaction. Capture commits and releases the Session lock
before the Turn completes, so an Artifact deletion can commit in between. The
terminal transaction holds the Session lock that also orders Artifact deletion,
and only one Turn per Session can be active, so the decision sees exactly the
Artifacts that remain at completion. Unchanged staged rows are deleted and their
private large objects unlinked in that transaction; published rows are never
modified.
- "Newest" follows the producing Turn's database creation time, then its ID.
Publication time can come from the Runtime's reported completion and is not a
reliable order between Turns.
- Known difference from the batch plan's wording, accepted as a local decision:
the plan republishes a path whose newest Artifact was deleted, but Core compares
against the newest *remaining* published Artifact. Deletion is physical and
leaves no record, and adding one would need a schema change outside this batch.
Example: Turn 1 publishes `b.txt` as `bravo`, Turn 2 publishes `bravo-v2`, and
the Turn 2 Artifact is then deleted. A later Turn whose `b.txt` is `bravo-v2`
republishes it, because the remaining Turn 1 version differs. A later Turn whose
`b.txt` is `bravo` publishes nothing, because the remaining Turn 1 Artifact
already has those bytes. The official behavior for this case is unobserved.
- A malformed filter resolves to the never-assigned maximum UUID, as for
malformed path identifiers, so it matches nothing without a database text
comparison. An empty `environment_id=` still means no filter.

Deferred and unchanged: a linked `outputs` root, hard links, FIFOs, sockets,
devices and device crossings still reject the whole capture and fail the Turn
with `artifact_capture_failed`; there is no official evidence for them yet.
Republication after changed bytes is inferred rather than observed, and the
deleted-newest case above is unobserved. The unknown `after` cursor (HE-57)
belongs to ERROR-PROTOCOL-001. Subagent lists keep their `data`/`has_more`
envelope until there is official Subagent evidence. Artifact IDs keep the Core
UUID format. Paths removed from the workspace keep their Artifacts.

Rust tests cover every link kind, including absolute links to a secret outside
the workspace and a relative link to a workspace file outside `outputs/`; an
inotify watch proves no target is opened or read, with a positive control. They
also keep the hard-link, socket, FIFO, linked-root and concurrent-change
rejections. Real-PostgreSQL store tests cover new, unchanged, changed,
changed-back, deleted-then-unchanged and deleted-during-capture paths, a deletion
that holds the Session lock while Turn completion waits, Turn-ordered newest
versions with inverted publication times, Session scoping and private object
accounting. Handler and real-PostgreSQL HTTP tests
cover the envelope, other, foreign and malformed filters, and foreign or missing
Sessions. The pinned-SDK and raw HTTP verifier used by live acceptance runs
against PostgreSQL across three Turns. Real Core, daemon and model acceptance is
recorded separately by the coordinator.
20 changes: 17 additions & 3 deletions contracts/agents-api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1374,11 +1374,22 @@ definitions:
items:
$ref: '#/definitions/v1.SessionArtifact'
type: array
first_id:
type: string
x-nullable: true
has_more:
type: boolean
last_id:
type: string
x-nullable: true
object:
enum:
- list
type: string
required:
- data
- has_more
- object
type: object
v1.SessionDeleted:
properties:
Expand Down Expand Up @@ -3265,8 +3276,10 @@ paths:
/agents/sessions/{session_id}/artifacts:
get:
description: Lists published outputs independently of Environment availability.
Sorting uses publication time and ID. The local default page size is 20; exact
upstream defaults and error parity remain unverified.
Sorting uses publication time and ID. A later Turn publishes a path again
only when it is new, its bytes changed, or no Artifact remains for it. A malformed
environment_id matches nothing. The local default page size is 20; exact upstream
defaults and error parity remain unverified.
parameters:
- description: agents=v1
in: header
Expand All @@ -3278,7 +3291,8 @@ paths:
name: session_id
required: true
type: string
- description: Producing Environment ID
- description: Producing Environment ID; an unknown or malformed ID returns
an empty page
in: query
name: environment_id
type: string
Expand Down
11 changes: 6 additions & 5 deletions contracts/agents-api/operation-evidence.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Repository paths below are relative to the inspected worktree; private evidence
| W | `~/.parsar/remediation/20260923/file-resource-semantics/official-files/` and `official-skills/`: 56 owned resource requests, no Sessions/models; [qualified observations and limitations](file-resource-semantics.md). |
| X | [Validation error fields](official-semantics-alignment.md#validation-error-fields--september-23); private `~/.parsar/remediation/20260923/campaign-scan-1/{vaults-agents,sessions,skills-files-templates}/findings.json` VA-07/08/09/10, SES-28, SFT-20 and the September 22 Session `metadata.a` null observation. Metadata/name field errors, U+0000 local limit, malformed path IDs and Template network codes; Go handler and real-PostgreSQL route/no-write tests, no model execution. |
| L | [List query tolerance](list-query-semantics.md#list-query-tolerance--september-23-2026); private `~/.parsar/remediation/20260923/campaign-scan-1/{vaults-agents,sessions,skills-files-templates}/findings.json`: owned-collection unknown/repeated keys, limit bounds, Vault status union and Files empty purpose, plus unknown keys on a deleted Vault read and Agent delete. Rows A1–D2 of that section; no model execution. |
| Y | [Artifact capture and listing](official-semantics-alignment.md#artifact-capture-and-listing--september-23); private `~/.parsar/remediation/20260923/campaign-scan-2/hosted-env/findings.json` HE-50..62 with raw records under `official/` (labels `al01`–`al09`, `ar01`–`ar04`, `ac01`–`ac05`, `ad01`/`ad02`): three owned Sessions and two tiny Turns, all deleted; first official Artifact observations. Rows A1–A4 of that section: symlink skip, republication, list envelope and malformed filter. Rust link tests, real-PostgreSQL store/HTTP and pinned-SDK tests without a model; live acceptance is recorded with the batch. |

## Per-operation evidence matrix

Expand All @@ -60,10 +61,10 @@ Paths in the appendix include `/v1`. SDK names here omit `client.`. `P` means pa
| 13 | beta.agents.sessions.turns.retrieve | P: persisted root/child Turn identity; malformed ID equals missing | H/S contain Turn list payloads; no isolated positive retrieve raw request identified in this set; X SES-28 official `turn_<random>` 404 | Recorded H/B scoped Turn recovery; C history uses list | Distinguish list-shape evidence from retrieve wire qualification; full lifecycle/usage |
| 14 | beta.agents.sessions.turns.list | P: full envelope, ordered root+child history; limit outside 1–100 rejects with the Beta code | S `turns-final/empty-page/limit-high/order-empty.json`; H both directions; L SES-14/15/16/17; X SES-28 | C Live paging; H/B real child/root identity recorded; L DB tenant A/B | All interleavings, same-timestamp paging, interim/failed usage |
| 15 | beta.agents.sessions.items.list | P: scoped root Items, full envelope; limit 0/above 100 clamp within the pinned 1–100 page | S `items-final/empty-page/limit-high.json`; H both directions; L SES-12/13/15/16/17 | C Live; H/T recorded content/coordination/result variants; L DB tenant A/B | Full Item union. Newer turn_id filter excluded from pin |
| 16 | beta.agents.sessions.artifacts.retrieve | P: immutable captured metadata | None located | D recorded live Docker/user-managed workspace output | Exact hosted metadata/default/error and capture-edge parity |
| 17 | beta.agents.sessions.artifacts.list | P: scoped stored list | None located | D recorded live output enumeration | Paging during capture/delete; unchanged-file republishing |
| 18 | beta.agents.sessions.artifacts.delete | P: stored deletion | None located | D recorded workflow summary; per-case remote evidence not re-read | Repeated/in-flight deletion and physical retention parity |
| 19 | beta.agents.sessions.artifacts.content | P: immutable download after Runtime loss | None located | D recorded live retained download | Range/content headers, cancellation-edge capture and partial-transfer parity |
| 16 | beta.agents.sessions.artifacts.retrieve | P: immutable captured metadata; unchanged outputs keep their Artifact ID across Turns | Y HE-58/59 `ar01-retrieve`, missing and `ar03-cross-session` 404 (fields match; official `artifact_` IDs vs Core UUIDs) | D recorded live Docker/user-managed workspace output; Y DB unchanged-path ID and metadata stability | Error messages differ; hard-link/special-file capture edges unobserved |
| 17 | beta.agents.sessions.artifacts.list | P: scoped stored list, common list envelope; later Turns publish only new, changed or no-remaining-Artifact paths; symlinks skipped; malformed `environment_id` gives an empty page | Y HE-50..56 `al01`–`al09`: Turn 1 capture with a skipped link, Turn 2 republication, envelope, order/cursor/limits, other and malformed filters | D recorded live output enumeration; Y Rust link tests, DB republication, HTTP envelope/filter/tenant and pinned-SDK checks | Unknown `after` (HE-57, ERROR-PROTOCOL-001); changed-bytes republication inferred; deleted-newest edge and paging during capture/delete unobserved |
| 18 | beta.agents.sessions.artifacts.delete | P: stored deletion; the next Turn republishes a deleted path | Y HE-61 `ad01-delete`, `ad02-delete-repeat` 404, reads after delete 404; HE-52 deleted path republished by Turn 2 | D recorded workflow summary; Y DB deleted-then-unchanged and deleted-during-capture republication | In-flight deletion and physical retention parity |
| 19 | beta.agents.sessions.artifacts.content | P: immutable download after Runtime loss | Y HE-60/62 `ac01-content`, `ac02-content-range` (Range ignored, full 200), `ac05` 404 after Session deletion | D recorded live retained download; Y DB earlier versions keep their bytes | Core's extra Content-Disposition; cancellation-edge capture, partial transfer and content after Environment expiry unobserved officially |
| 20 | beta.agents.sessions.subagents.retrieve | P: owned durable child identity/lifecycle | None located | B recorded Live six-read matrix | Full lifecycle/multi-agent parity; native close differences |
| 21 | beta.agents.sessions.subagents.list | P: direct/nested/closed child records | None located | B recorded Live scopes/pages/continuation | Publication timing/parent propagation and unsupported native nesting |
| 22 | beta.agents.sessions.subagents.items.list | P: child-owned history only | None located | B recorded Live child separation/recovery | Full child Item union; continuous child progress not qualified |
Expand Down Expand Up @@ -110,7 +111,7 @@ Paths in the appendix include `/v1`. SDK names here omit `client.`. `P` means pa
2. **Session differences:** The Session admission batch removes idle `none` creation and empty metadata update. Local durable creation idempotency remains an explicit difference. Whitespace-only input succeeds officially but is rejected by the existing Core message validator; this newly observed difference is queued separately. Session agent updates, newer Environment shapes and root Item turn_id are baseline-upgrade questions.
3. **Template/Skill composition:** shared env/files/setup/packages selection is covered by the composition batch; template-reference null network/capability lists are covered by the null-selection batch. Official derived capability-directory projection remains different. Skill content/default metadata are covered by file-resource-semantics.md; sole-version deletion, visibility and broader numbering/error behavior remain unverified.
4. **Execution coverage:** use T's qualified matrix, not a blanket missing-image/structured-output claim. MiniMax functions/service MCP, optional tool combinations, unsupported images/placements and broader native lifecycle are explicit restrictions. PTC omission retains approved native behavior; Claude/MiniMax public Usage remains null; child settlement cadence/native close limits remain visible. No second executor/model loop or guessed counters are justified.
5. **Workspace and resources:** live Files bounds, symlink/path/cursor choices, artifact overwrite/republishing/headers/cancellation edges, full Environment metadata/lifecycle and Vault archive/in-flight-token semantics remain partial or unknown. Retired Core-managed E2B acceptance cannot qualify current user enrollment.
5. **Workspace and resources:** live Files bounds, symlink/path/cursor choices, artifact capture edges for hard links/special files and cancellation (Y aligns output symlinks, republication and the list envelope), full Environment metadata/lifecycle and Vault archive/in-flight-token semantics remain partial or unknown. Retired Core-managed E2B acceptance cannot qualify current user enrollment.

## Enumeration and wording mismatches

Expand Down
3 changes: 3 additions & 0 deletions contracts/agents-api/v1/session_artifacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ type SessionArtifact struct {
}

type SessionArtifactList struct {
Object string `json:"object" enums:"list" binding:"required"`
FirstID *string `json:"first_id" extensions:"x-nullable"`
LastID *string `json:"last_id" extensions:"x-nullable"`
Data []SessionArtifact `json:"data" binding:"required"`
HasMore bool `json:"has_more" binding:"required"`
}
Expand Down
3 changes: 3 additions & 0 deletions packages/codex-executor/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ fn walk<W: Write>(
)?;
append(File::from(file), &path, device, budget, archive)?;
}
// A link is recognized by its lstat type and skipped: it is never
// followed, opened or resolved, and publishes no Artifact.
FileType::Symlink => {}
_ => return Err(invalid()),
}
}
Expand Down
Loading
Loading