Skip to content
PortotifyPublic

About

An experimental consequence-boundary primitive for agentic systems.

Topics

Resources

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Latest commit

 

History

38 Commits

Folders and files

Repository files navigation

SwampBox

Stopping an agent does not necessarily stop the consequences it has already set in motion.

A process can end while an artifact, delegated task, credential, or initiated external action persists. Process isolation can constrain the agent's runtime without independently establishing whether surviving consequences may be used or released later.

Process containment is not consequence containment. SwampBox explores the latter by modeling custody, lineage, and a release boundary for specific effects under externally supplied authority. This public reference implementation does not claim to intercept every downstream effect, revoke third-party credentials, or undo completed actions.

SwampBox is a provider-neutral reference model for containing modeled consequences, not a process sandbox. A process sandbox isolates running code; SwampBox models where a consequence may be visible and which execution holds custody.

The current reference implementation demonstrates execution-scoped consequence containment, detached snapshots, explicit custody transfer, and a provider- neutral release boundary. Authority comes from an external provider. SwampBox binds that decision to the consequence and current custodian, checks material and the decision's finite validity window, and passes the bound snapshot to an actuator.

This is about consequences surviving a process lifetime, not traditional sandbox escape.

Persistence is not authority.

Scope of this repository

This repository is the public reference surface of SwampBox, not the full scope of the project. It is an inspectable implementation with an offline test suite and, where stated, recorded experiments, and it can be run and reproduced on its own terms. It covers consequence containment and the primitives around it: custody, adoption, lineage, and a release boundary in front of an external effect.

SwampBox is concerned with consequences after they are produced: how they remain contained, retain lineage, move between execution contexts, acquire custody, and approach a boundary where an external effect may occur, and, at the governance level, whether that specific effect should cross the boundary and whether the authority supplied for it remains valid at that point. This repository implements a defined part of that surface.

SwampBox also has an active non-public architecture and implementation track covering a broader governance and enforcement surface. That work is intentionally outside this repository's disclosure boundary: its internal architecture, implementation details, integration mechanics, and proprietary material are not published here.

This separation defines the project's disclosure boundary. The public reference implementation stands on its own documented behavior, test suite, and recorded evidence. The existence of non-public work does not by itself establish any undocumented capability, guarantee, or resolution of a limitation documented here.

Prerequisites and test paths

The minimum supported Python baseline is Python 3.12 or newer. Compatibility with newer Python releases remains subject to available test evidence; this baseline does not claim that every newer release has been validated.

The reference implementation and non-MCP offline paths use the Python standard library. The optional MCP surface has its own pinned dependency file, described in the MCP contract.

All repository tests use unittest. The reference/core test path is:

python -m unittest tests.test_swampbox -v

The complete discovered suite is:

python -m unittest discover -s tests -v

That complete suite also discovers the MCP contract tests, so install the optional MCP dependency before running it from a fresh clone:

python -m pip install -r requirements-mcp.txt

Current reference implementation

The in-memory consequence store provides execution-scoped visibility and custody. A contained consequence has an origin execution and a current custodian. Reading, submitting, and transferring are scoped to registered executions, and returned consequence snapshots are detached from internal mutable state.

Here, execution_id is a caller-supplied logical handle used by the reference implementation to identify registered executions and scope store operations. Registration establishes store state, not authentication, and returns no authentication credential: an execution_id is not an authenticated principal or credential, and the reference implementation does not establish that the in-process caller supplying it is the execution represented by that identifier. Store checks such as registration, ACTIVE state, a supplied source or release ID equaling the current custodian, and AuthorityDecision.execution_id matching the custodian are state or binding predicates; they do not establish who the Python caller is or that the caller independently authenticated as that execution.

Explicit transfer moves custody and visibility from one registered execution to another. It does not release or execute the consequence, grant permission, or delegate authority.

transfer(source_execution_id, target_execution_id, consequence_id) is an immediate custody transition, not a two-phase handoff. After the relevant execution and state predicates pass, the target becomes the current custodian and visibility moves immediately; there is no pending recipient awaiting acceptance or adoption.

Adoption is separate from transfer. adopt(execution_id, consequence_id) applies to a contained consequence whose current custodian is None (quarantined). No designated recipient is stored for such a consequence, and any registered ACTIVE execution may adopt it under the existing predicates; adoption then makes that execution the current custodian. When an execution ends, consequences it holds that remain in release state CONTAINED become quarantined and adoptable. Thus, “B adopts X” means an explicit state transition requested using B's logical execution handle, not acceptance of a previous transfer or authenticated consent.

current_execution_id is state-dependent: for a CONTAINED consequence, a non-None value identifies its current logical custodian. For a RELEASED or UNCERTAIN consequence, it may retain the last custodian's execution ID after that execution becomes ENDED. The retained ID alone does not establish an active execution, read access, or authority. Quarantine requires both CONTAINED state and current_execution_id is None; ending an execution does not make its terminal consequences adoptable, transferable, or eligible for another supported release. origin_execution_id and, where present, parent_consequence_id preserve separate provenance, not a complete custody history.

Custody establishes visibility and current custody, not release authority or effect admissibility. Moving custody from A to B does not turn authority bound to A into authority for B; the release path still performs its separate authority checks. Adoption likewise does not establish current or continuing authority, authority freshness, or revocation state. This is a cooperative in-process reference model that trusts application or integrator code to present logical execution handles correctly; integrations that need authenticated caller identity must establish that property outside this model.

TRANSFER != RELEASE.

Consequence lineage

The reference model also preserves a narrow single-parent relationship for derived consequences. A consequence may name one existing parent consequence when the submitting execution currently holds that parent's custody and the parent is still contained. A quarantined parent must first be explicitly adopted; inspection alone is not custody.

In the minimal lifecycle, A creates X, A ends, B adopts X, and B may submit C with X as its parent. The parent edge is preserved when custody later moves, but it is provenance metadata, not material effect identity. Persistence is not inheritance, adoption is not authority, and lineage is not authority.

X's authority does not become C's authority. A derived consequence still requires its own externally supplied authority decision, bound to that consequence and valid within its provider-defined window at release, before it can cross the supported release boundary. Multi-parent lineage and ancestry traversal are not supported.

Supported release boundary

A contained consequence does not become released merely because it exists, matches material, or has a current custodian. The supported ReleaseBoundary path requires an external, provider-neutral authority decision bound to the consequence, current custodian, and exact modeled material (defined under Material equality). The boundary rechecks custody, material, and the decision's temporal validity immediately before supported actuation. An ALLOW result alone is not actuator success.

In this reference implementation, freshness means temporal validity. The provider defines each decision's validity window, and the boundary requires that window to be well formed (valid_until after issued_at) and to contain the current time (issued_at at or before it, valid_until after it). AuthorityProvider.evaluate is called once per release attempt. The final check immediately before actuation applies the same test to the same returned decision against a later clock read, which catches a decision that expires between the first validation and the actuator call. It does not call the provider again or consult any revocation or policy source, so a change to the underlying authority inside an otherwise-valid window is not independently observed; observing such changes would need additional authority-protocol semantics that this reference does not define. Freshness here does not mean that a decision is recently issued, newly created, unique, one-shot, or unused, and decision_id is not a replay or idempotency key.

The actuator reports the result of the supported attempt:

  • CONTAINED means the consequence did not successfully cross the supported actuator boundary;
  • RELEASED means the actuator reported definite success;
  • UNCERTAIN means the external effect may have occurred, but its outcome could not be established. UNCERTAIN is not equivalent to failure and is not an automatic retry signal.

This release path is an in-memory, cooperative reference model. It does not authenticate execution identities, provide durable crash reconciliation or distributed atomicity, guarantee exactly-once external effects, or enforce universal routing of every effect-capable path through SwampBox (see Direct actuator bypass).

Within one store instance, the reference store orders operations with a single in-process lock, and that lock is held through the final release critical section. Authority evaluation happens outside that section. The section covers revalidating state and custody, the final material and freshness checks, the actuator call, and committing the outcome, so conflicting store mutations cannot interleave with a checked effect attempt. The lock is not authority and does not replace the freshness check: authority still decides whether the effect may proceed, and the lock only keeps the same store's state from changing underneath that attempt.

The cost is availability. A slow or blocked actuator can delay other operations on the same store instance, including reads and unrelated consequences, and ReleaseBoundary instances that share a store share this limit. Separate store instances are independent. The reference implementation imposes no actuator timeout, though an individual actuator may impose its own. Narrowing the lock is not a transparent optimization: it would need an explicit concurrency design that preserves state and custody stability, duplicate-release exclusion, authority freshness at the effect boundary, and post-actuation finality. The lock is in-process and in-memory only. It provides no multi-process or multi-host coordination, does not involve the external system, and does not provide exactly-once delivery.

Material equality

"Exact modeled material" means type-strict structural value equality of a consequence's consequence_type, target, and payload. Corresponding values must have the same type, so 1 does not match 1.0, and True does not match 1. Mappings compare by key and recursively compared value, not by key order. Sequences compare in order. Strings compare by exact value, with no Unicode normalization, case folding, or whitespace normalization. Floating-point +0.0 and -0.0 are the same material value.

This is a strict comparator, not a fuzzy one, but it does not compare representations. Release admissibility does not compare serialized bytes, repr output, or JSON text, so mapping key order and the sign of a zero are not authority-bearing distinctions, even though a provider or actuator can observe them. Serialization does not define admissibility, and this project defines no canonical serialization. This equality definition does not broaden the canonical store's accepted payload types.

The public reference model separately bounds structural admissibility of payload: its maximum container depth is MAX_MATERIAL_DEPTH = 16. Scalar leaves have depth 0; the first list or dict has depth 1, and each nested list or dict adds one. Dict keys do not add depth. Cyclic containers are rejected; shared acyclic references are allowed. Material beyond this bound is outside the supported domain: consequence submission raises ValueError before storage, while over-depth authority-decision material is treated as authority_invalid before material comparison or actuation. target and consequence_type are exact strings, not recursive material. This admissibility rule does not change material equality and does not bound total nodes, width, string length, or aggregate memory use.

Why this problem matters

The earlier inheritance scenario remains useful evidence for the narrower consequence-survival thesis:

An agent process may end while an artifact containing state, an instruction, or a proposed task remains available to another consumer. The artifact can survive the process lifetime without carrying the originating agent's permission into the future.

Legacy inheritance scenario

Agent A persists a consequence-capable task and its modeled role ends. Agent B later encounters the task. The artifact's persistence and origin do not grant permission to execute it; Agent B must obtain a new admission result before the downstream consequence may commit.

See the inheritance experiment for the full synthetic sequence and acceptance matrix.

The legacy synthetic prototype models one synthetic flow:

proposed consequence
    -> admission
    -> controlled commit
    -> receipt

Only an explicit ALLOW for the exact proposed consequence may reach the simulated actuator. Missing, malformed, HOLD, DENY, and mismatched admission states fail closed. This legacy admission path is separate from the provider-neutral ReleaseBoundary path described above.

SwampBox is an open-source project by Portotify. The reference experiments remain provider-neutral and have no dependency on Portotify Core.

Optional MCP surface

The repository also includes an optional MCP material-comparison surface with exactly one tool, compare_consequence_binding. It deterministically compares the modeled consequence_type, target, and payload fields of a supplied declaration and proposal, returning MATERIAL_MATCH or MATERIAL_MISMATCH. It is outside the containment and release runtime: it does not expose the consequence store or ReleaseBoundary, establish authority, grant permission, release a consequence, or invoke an actuator.

See the MCP contract for the exact schema, semantics, and local Streamable HTTP instructions.

From the repository root, install the pinned MCP runtime in a local virtual environment and start the server with:

python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements-mcp.txt
.\.venv\Scripts\python.exe mcp_server.py

What is included

Synthetic inheritance experiment

The synthetic inheritance path is the simplest conceptual entry point. It is implemented with the Python standard library, in-memory artifact storage, a deterministic synthetic admission provider, an exact-match boundary, a non-delivering simulated actuator, and generic receipts.

This synthetic path is intentionally in-memory and does not contact real APIs, source-control systems, ticket systems, queues, mail systems, cloud services, or business systems. Its inheritance experiment documents the original synthetic sequence and acceptance matrix. Its legacy admission and simulated-actuation code lives in experiments/legacy_synthetic.py, outside the canonical reference.swampbox release surface.

Controlled GitHub effect experiment

This is a separate controlled experiment path, not universal interception and not the provider-neutral ReleaseBoundary runtime. SwampBox admitted an exact GitHub issue consequence, crossed the create boundary once without retry, and a later independent read found exactly one matching issue; immediate post-create verification remained uncertain.

The controlled GitHub effect experiment is an explicit opt-in reference path. The default tests remain offline. The live path requires GitHub CLI (gh) to already be available and authenticated for the operator-controlled target. Authentication is not modeled as consequence authority. The consequence is the exact repository/title/body contract; the path performs no automatic create retry. A direct bypass remains outside the demonstrated containment.

Live external-effect validation

A later controlled run used the canonical consequence lifecycle and the provider-neutral ReleaseBoundary against a disposable private GitHub repository. An authority decision bound to the parent consequence, and one rebound to the derived consequence but carrying the parent's material, were each rejected by the boundary with no external write. A fresh decision bound to the derived consequence then permitted one GitHub issue-creation attempt, and a read-only check observed the issue. In this experiment, lineage did not substitute for release authority.

This was one run with one effect and a scripted in-process authority provider. It did not compare a real sandbox, inject authority revocation, exercise crash recovery, or establish production readiness. See the experiment record for methodology, evidence identifiers, and limits.

Direct actuator bypass

The simulated actuator exposes a low-level method so the bypass assumption is visible. The normal flow calls it only through the boundary.

If a real actuator can be reached directly while bypassing the SwampBox boundary, consequence containment has not been established. The synthetic prototype does not claim to solve that deployment problem. The controlled GitHub experiment likewise does not prevent a direct bypass outside the demonstrated path.

The supported release path routes effect attempts through ReleaseBoundary. In the supported composition it is the gate immediately before an actuator is invoked, and its authority, custody, material, and temporal checks apply to each attempt made through it. The reference implementation does not technically force effect-capable code to use it, and it does not defend against in-process code that deliberately calls an effect sink directly. Application or integrator code that constructs the composition, or that holds an actuator or a direct external credential, is inside this model's trust boundary: it is trusted to keep effects on the supported path. That is an assumption of the model, not a claim that such code is safe, and SwampBox does not authenticate or constrain it. Consequence containment is therefore a property of an integration that preserves this routing, not of the boundary alone.

An actuator is an effect sink, not an authority boundary. It executes effects that have already been admitted and is not expected to repeat custody, binding, or authority checks; those belong to the boundary. AuthorityProvider is the authority source supplied to the boundary and does not enforce routing. Both are injected dependencies, which is intended composition: when ReleaseBoundary.release is used, the actuator is reached only after the boundary's checks. Python importability and underscore naming are not enforcement boundaries, and direct mutation of private implementation state by in-process code is outside the supported API. Stronger technical enforcement would need an additional architectural boundary that this reference implementation does not provide.

Non-claims

These reference paths do not claim to solve sandbox escape, prevent all downstream effects, provide a secure sandbox, provide complete agent security, grant or independently determine authority, establish production-grade containment, be production ready, or be an industry standard. They do not claim exactly-once behavior, prevention of every duplicate consequence, universal interception, direct-bypass prevention, or immediate verified success. The synthetic and GitHub paths retain the narrower scopes described in their experiment documents.

Run

All public tests use Python's standard-library unittest runner. From this directory, the reference/core test path is:

python -m unittest tests.test_swampbox -v

The complete public suite is:

python -m unittest discover -s tests -v

The complete discovered suite includes the MCP contract tests. Install the optional MCP dependency first when using that command from a fresh clone:

python -m pip install -r requirements-mcp.txt

The live GitHub path is explicit and manual. Public documentation does not provide a copy-paste mutation command.

About

An experimental consequence-boundary primitive for agentic systems.

Topics

Resources

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages