flowjet 2.1.4__tar.gz → 2.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flowjet-2.1.4 → flowjet-2.1.6}/CHANGELOG.md +52 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/PKG-INFO +1 -1
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/RFC-005-arcbox-tool-sandbox.md +68 -3
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/rfc-history.md +1 -1
- {flowjet-2.1.4 → flowjet-2.1.6}/pyproject.toml +1 -1
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/stream.py +33 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/base.py +53 -13
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/sandbox/arbox.py +359 -21
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/sandbox/settings.py +43 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/acp/agent.py +2 -6
- {flowjet-2.1.4 → flowjet-2.1.6}/.gitignore +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/LICENSE +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/README.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/config/nano-local.yml +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/config/nano-sandbox-soothe.yml +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/config/nano.yml +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/deploy/README.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/completion-design.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/impl/IG-001-openai-compatible-server.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/impl/IG-002-isolated-thread-pool-runtime.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/impl/IG-003-production-isolation-hardening.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/RFC-001-openai-compatible-api.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/RFC-002-isolated-thread-pool-runtime.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/RFC-003-production-isolation-hardening.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/RFC-004-unified-package.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/rfc-index.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/docs/specs/rfc-namings.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/examples/README.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/__main__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/README.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/gws-drive/SKILL.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/gws-drive-upload/SKILL.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/.env.example +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/README.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/SKILL.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/assets/background_library.html +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/assets/showcase_template.html +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/references/background_styles.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/references/design_patterns.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/references/webgl_backgrounds.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/requirements.txt +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/scripts/generate_showcase.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/logo-generator-skill/scripts/svg_to_png.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/SKILL.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/brainstorming/spec-document-reviewer-prompt.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/coding-plan-template.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/implementation/impl-guide-template.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/review/pr-review-template.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/review/review-checklist.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/rfc-template.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-architecture-design.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-conceptual-design.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/specs/template-impl-interface-design.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-drafts-readme.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-impl-readme.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-platonic.yml +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-history.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-index.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-namings.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/assets/templates/template-rfc-standard.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/BRAINSTORM/brainstorm.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-code.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-create-guide.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-full.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-update-guide.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/IMPL/impl-validate-guide.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-plan-modular-specs.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-architecture.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-conceptual.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-recover-impl-interface.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scaffold.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/INIT/init-scan.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/REFERENCE.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/REVIEW/review-spec-compliance.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-compliance.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-check-taxonomy.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-history.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-index.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-generate-namings.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-refine.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/SPECS/specs-validate-consistency.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-overview.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-1.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-2.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/platonic-coding/references/WORKFLOW/workflow-phase-3.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/SKILL.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-deep.json +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/evidence-first.json +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/idea-first.json +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/loop.json +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/paper-pipeline.json +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/rapid.json +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/rigor-first.json +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/stance-first.json +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/systematic-review.json +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/patterns/verify-loop.json +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/references/backends.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-ars-omr.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/references/handoff-omr-ars.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/references/routing.md +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/scripts/csljson_to_omr.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/builtin_skills/research-bootstrap/scripts/papers_index_to_csljson.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/__main__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/agent.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/cli.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/completion/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/completion/context.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/completion/engine.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/doctor_cmd.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/doctor_fix_cmd.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/progress.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/serve_cmd.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/setup_cmd.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/shell/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/shell/_fj.zsh +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/shell/fj.bash +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/threads.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/cli/tool_stream.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/_warnings.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/config.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/factory.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/skills.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/wiring.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/agent/workspace.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/backend_config.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/nano/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/nano/adapter.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/nano/backend.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/nano/interrupts.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/nano/mapping.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/soothe/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/soothe/adapter.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/bridges/soothe/backend.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/events.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/identity.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/modes.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/protocol.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/direct.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/fake.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/adapter.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/admission.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/backend.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/errors.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/mode_gate.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/pool.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/request.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/runtime/isolation/workspace.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/sandbox/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/sandbox/resolver.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/streaming/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/streaming/presentation.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/streaming/tool_results.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/core/streaming/tool_stream.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/home.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/__main__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/acp/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/acp/routes.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/config.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/http/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/http/app.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/http/auth.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/__init__.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/errors.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/projection.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/routes.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/schemas.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/service.py +0 -0
- {flowjet-2.1.4 → flowjet-2.1.6}/src/flowjet/server/openai_compat/store.py +0 -0
|
@@ -10,6 +10,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [2.1.6] — 2026-10-24
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **ArcBox tool sandboxes stay usable across recycles, crashes and restarts.**
|
|
18
|
+
The manager trusted its in-memory lease map, and the agent graph pinned its
|
|
19
|
+
sandbox id at build time, so a guest that died behind a still-`ready` daemon
|
|
20
|
+
record (host sleep, idle recycle, daemon restart) left the worker talking to a
|
|
21
|
+
corpse: every file/shell tool failed and the agent reported an empty
|
|
22
|
+
workspace even though the host tree was fully staged. `open_workspace` now
|
|
23
|
+
probes readiness once per turn — a control-plane `info()` tier that catches a
|
|
24
|
+
record outliving its VM plus terminal states, then a guest round-trip that
|
|
25
|
+
catches a wedged guest — and kills, drops and replaces a lease that fails.
|
|
26
|
+
Every new guest, whether spawned or adopted, is seeded from the host
|
|
27
|
+
workspace, and in-flight run counts carry over so peer open/close pairs stay
|
|
28
|
+
balanced. The agent cache key is now workspace **+ sandbox id**, so a respawn
|
|
29
|
+
rebuilds the graph against the live guest.
|
|
30
|
+
- **Crash and restart recovery.** VMs are created with a labelled orphan
|
|
31
|
+
deadline (`idle_timeout` + `on_idle=kill`) that the reaper re-arms every 60 s,
|
|
32
|
+
so a process that dies leaves nothing behind — the daemon reaps it, with no
|
|
33
|
+
cross-process coordination and no startup kill-list. On first use the manager
|
|
34
|
+
scans owner-scoped sandboxes and adopts a surviving VM, pulling guest state to
|
|
35
|
+
the host before pushing (recovering a crashed turn's artifacts) instead of
|
|
36
|
+
spawning a duplicate; a failed create reconnects the client and retries once.
|
|
37
|
+
- **Every control-plane RPC is bounded** (`kill`/`set_lifecycle` 15 s, `connect`
|
|
38
|
+
by `connect_timeout`). The SDK's per-RPC deadline is opt-in and was
|
|
39
|
+
unconfigured: one wedged call stalled the reaper loop, and a stalled heartbeat
|
|
40
|
+
reads to the daemon exactly like a dead owner — which gets healthy, in-use VMs
|
|
41
|
+
reaped at their deadlines.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- `sandbox.orphan_timeout_seconds` defaults to `max(900, 3 × idle_timeout_seconds)`
|
|
46
|
+
instead of a fixed 900, and an explicit value is validated only when the
|
|
47
|
+
sandbox is enabled — existing configs keep parsing unchanged. New keys:
|
|
48
|
+
`recover_orphans` (adopt surviving VMs after a restart) and `owner` (label
|
|
49
|
+
namespace, env `FLOWJET_SANDBOX_OWNER`) so planes sharing one ArcBox daemon
|
|
50
|
+
never adopt or reap each other's sandboxes. See RFC-005 §5.1/§5.2.
|
|
51
|
+
|
|
52
|
+
## [2.1.5] — 2026-10-24
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **`ask_user` pauses render as Markdown on ACP/UI hosts.** A clarification
|
|
57
|
+
turn's reply reused the CLI terminal block (space-indented `⏸ Paused for
|
|
58
|
+
input:` followed by `• option` lines). Hosts that render replies as Markdown
|
|
59
|
+
collapse the indentation, so the pause and its options displayed as a single
|
|
60
|
+
run-on paragraph. The ACP path now emits `format_questions_markdown()`: a
|
|
61
|
+
bold pause heading, a bold question (Q1:/Q2: numbering for multiple
|
|
62
|
+
questions), and options as `- **label** — description` bullets, keeping the
|
|
63
|
+
`(Recommended)` → ★ convention. The CLI keeps the plain-text block.
|
|
64
|
+
|
|
13
65
|
## [2.1.4] — 2026-10-23
|
|
14
66
|
|
|
15
67
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flowjet
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.6
|
|
4
4
|
Summary: FlowJet — agent CLI (fj) and server (ACP + OpenAI-compatible HTTP) powered by Soothe
|
|
5
5
|
Project-URL: Homepage, https://github.com/caesar0301/flowjet
|
|
6
6
|
Project-URL: Repository, https://github.com/caesar0301/flowjet
|
|
@@ -68,6 +68,9 @@ sandbox:
|
|
|
68
68
|
connect_timeout: 60
|
|
69
69
|
idle_timeout_seconds: 300 # idle sandbox recycle
|
|
70
70
|
max_sandboxes: 8 # 0 (unlimited), None (70% usable CPU / vcpus, min 8), or int
|
|
71
|
+
orphan_timeout_seconds: null # daemon-side crash deadline; null → max(900, 3× idle)
|
|
72
|
+
recover_orphans: true # adopt a surviving VM after a restart
|
|
73
|
+
owner: flowjet # sandbox label namespace; env FLOWJET_SANDBOX_OWNER
|
|
71
74
|
# FLOWJET_SANDBOX_MAX (int; 0=unlimited) and FLOWJET_SANDBOX_MAX_PERCENT (1-100, default 70) env overrides
|
|
72
75
|
# api_url / api_key: optional; env fallback ARCBOX_API_URL / ARCBOX_API_KEY
|
|
73
76
|
# soothe-nano placement policy (pass-through):
|
|
@@ -92,7 +95,7 @@ flowchart LR
|
|
|
92
95
|
E[WorkspaceResolver<br/>/ SyncAware] -->|open: push host→guest<br/>close: pull guest→host| D
|
|
93
96
|
```
|
|
94
97
|
|
|
95
|
-
- `flowjet.core.
|
|
98
|
+
- `flowjet.core.sandbox.arbox.ArcBoxToolSandboxManager` — one guest VM
|
|
96
99
|
per workspace: lazy spawn (`AsyncArcBox.create(..., wait_until_ready=True)`),
|
|
97
100
|
idle reaper, LRU eviction at `max_sandboxes` (explicit int, `None`, or `0`
|
|
98
101
|
for unlimited — `None` sizes from host CPU: 70% usable ÷ per-sandbox vCPUs,
|
|
@@ -102,11 +105,73 @@ flowchart LR
|
|
|
102
105
|
runs inside the session lifecycle: open = inner materialize → host push;
|
|
103
106
|
close = guest pull → inner checkpoint (the sync checkpoint must see guest
|
|
104
107
|
outputs).
|
|
105
|
-
- Adapter binding — the sandbox backend is
|
|
106
|
-
adapter
|
|
108
|
+
- Adapter binding — the sandbox backend is pinned at agent build time, so the
|
|
109
|
+
adapter caches the built agent on **workspace + sandbox id**, not the path
|
|
110
|
+
alone: a respawn, idle recycle or eviction yields a new id and the agent is
|
|
111
|
+
rebuilt (a stale binding fails every guest tool call with `VM not found`). In
|
|
107
112
|
sandbox mode the request's `configurable["workspace"]` is the **guest root**
|
|
108
113
|
(`/workspace`), not the host path — matching soothe-nano sandbox semantics
|
|
109
114
|
(system prompt, workspace policy, and recon all target the guest root).
|
|
115
|
+
|
|
116
|
+
### 5.1 Readiness invariant
|
|
117
|
+
|
|
118
|
+
`open_workspace` runs once per turn on the main loop, *before* the agent
|
|
119
|
+
streams, and is the only place a guest is created or validated; the sync
|
|
120
|
+
`backend_for` (worker threads) never spawns. It guarantees a *usable* guest:
|
|
121
|
+
|
|
122
|
+
1. **Probe** — two tiers, cheap first. Tier 1 is `sandbox.info()`: one unary
|
|
123
|
+
RPC, "always fresh, never cached", no guest execution, no lifecycle side
|
|
124
|
+
effects — it catches the observed failure class (the daemon's record
|
|
125
|
+
*outliving the VM*: `list()` still says `ready` while every call fails
|
|
126
|
+
with `VM not found`) and terminal states (`stopping`/`stopped`/`failed`).
|
|
127
|
+
Tier 2 is a guest round-trip (`true`), catching a wedged guest (VM
|
|
128
|
+
present, nothing executing); commands run as independent process groups,
|
|
129
|
+
so it never queues behind a peer's long-running command. A record is
|
|
130
|
+
never trusted on its own.
|
|
131
|
+
2. **Replace** — a lease that fails the probe is killed and dropped; the
|
|
132
|
+
replacement is seeded from the host tree. In-flight run counts carry over so
|
|
133
|
+
peer open/close pairs stay balanced, and the reseed is forced even with a
|
|
134
|
+
peer mid-run (a new VM is empty — skipping the push is how materials go
|
|
135
|
+
missing).
|
|
136
|
+
3. **Reseed** — every path that yields a new guest (spawn, respawn, adoption)
|
|
137
|
+
pushes the host workspace before the turn runs.
|
|
138
|
+
|
|
139
|
+
Every control-plane RPC in the manager is bounded (`kill` / `set_lifecycle`
|
|
140
|
+
by a 15 s deadline, `connect` by `connect_timeout`): the SDK's per-RPC
|
|
141
|
+
deadline is opt-in and unconfigured here, and one wedged call would otherwise
|
|
142
|
+
stall the reaper loop — a stalled heartbeat reads to the daemon exactly like
|
|
143
|
+
a dead owner, which gets *healthy* VMs reaped at their deadlines.
|
|
144
|
+
|
|
145
|
+
### 5.2 Crash and restart
|
|
146
|
+
|
|
147
|
+
The manager holds its leases in memory, so a `flowjet-server` crash loses the
|
|
148
|
+
map while the daemon keeps the VMs. Three mechanisms keep that safe:
|
|
149
|
+
|
|
150
|
+
- **Daemon-side orphan deadline** — each VM is created with
|
|
151
|
+
`idle_timeout=<orphan deadline>` + `on_idle="kill"`; the manager
|
|
152
|
+
*re-arms* that timer every `_HEARTBEAT_INTERVAL_SECONDS` (60 s, one
|
|
153
|
+
bounded `set_lifecycle` per lease). Expiry therefore means "the owning
|
|
154
|
+
process is gone", and the daemon reaps the VM with no cross-process
|
|
155
|
+
coordination and no startup kill-list. A lease that cannot be re-armed is
|
|
156
|
+
unreachable and is dropped immediately. The deadline defaults to
|
|
157
|
+
`max(900, 3 × idle_timeout_seconds)` when unset, so an existing config with
|
|
158
|
+
a long idle reaper parses unchanged and is never preempted by a daemon
|
|
159
|
+
kill; an explicit value must exceed `idle_timeout_seconds` (validated only
|
|
160
|
+
when the sandbox is enabled).
|
|
161
|
+
- **Startup scan + adoption** — on first use the manager lists owner-scoped
|
|
162
|
+
sandboxes carrying its labels (`flowjet.sandbox{,-owner,-instance,-workspace}`).
|
|
163
|
+
Entries from another instance are recovery *candidates*, not victims: the
|
|
164
|
+
first turn for that workspace connects, probes, then **pulls guest → host**
|
|
165
|
+
(recovering files the interrupted turn wrote but never pulled) before pushing
|
|
166
|
+
the host tree back. No candidate (or a dead one) falls through to a fresh,
|
|
167
|
+
seeded VM. `recover_orphans: false` disables adoption.
|
|
168
|
+
- **Owner scoping** — `owner` namespaces the labels so several flowjet planes
|
|
169
|
+
sharing one daemon never adopt or reap each other's sandboxes. Adoption
|
|
170
|
+
additionally assumes one process per workspace at a time (the shipped
|
|
171
|
+
agentspace plane runs a single replica).
|
|
172
|
+
|
|
173
|
+
A failing `create` reconnects the client and retries once, covering a daemon
|
|
174
|
+
that is itself restarting.
|
|
110
175
|
- Credential boundary — LLM keys and the nano config never enter the guest;
|
|
111
176
|
only the staged workspace bytes do.
|
|
112
177
|
|
|
@@ -21,7 +21,7 @@ This document tracks all RFC lifecycle events in chronological order (newest fir
|
|
|
21
21
|
|
|
22
22
|
| Date | Event | RFC / Document | Summary |
|
|
23
23
|
|------|-------|----------------|---------|
|
|
24
|
-
| 2026-10-18 | Created | [RFC-005](RFC-005-arcbox-tool-sandbox.md) | ArcBox tool sandbox via soothe-nano 2.0; whole-worker arcbox isolation removed |
|
|
24
|
+
| 2026-10-24 | Updated | [RFC-005](RFC-005-arcbox-tool-sandbox.md) | §5.1 two-tier readiness probe + sandbox-id agent cache key; §5.2 crash/restart recovery (daemon orphan deadline, heartbeat, orphan adoption); bounded control-plane RPCs; orphan deadline derives when unset || 2026-10-18 | Created | [RFC-005](RFC-005-arcbox-tool-sandbox.md) | ArcBox tool sandbox via soothe-nano 2.0; whole-worker arcbox isolation removed |
|
|
25
25
|
| 2026-07-31 | Reference Updated | [TEST-COVERAGE-MATRIX](../impl/TEST-COVERAGE-MATRIX.md) | Feature → test attachment for RFC-001–003 |
|
|
26
26
|
| 2026-07-31 | Created | [IG-003](../impl/IG-003-production-isolation-hardening.md) | Production isolation hardening guide for RFC-003 |
|
|
27
27
|
| 2026-07-31 | Created | [RFC-003](RFC-003-production-isolation-hardening.md) | Production-ready ThreadPool lifecycle, bridge, and policy |
|
|
@@ -420,6 +420,39 @@ def format_questions_block(
|
|
|
420
420
|
return "\n".join(lines) + "\n"
|
|
421
421
|
|
|
422
422
|
|
|
423
|
+
def format_questions_markdown(questions: tuple[dict[str, Any], ...]) -> str:
|
|
424
|
+
"""Render ``ask_user`` questions as Markdown for hosts that render replies.
|
|
425
|
+
|
|
426
|
+
Same content as :func:`format_questions_block` (the CLI renders that block
|
|
427
|
+
to a terminal), but in Markdown so ACP/UI hosts show a bold pause heading
|
|
428
|
+
with bulleted, bolded options. The plain-text block cannot be reused here:
|
|
429
|
+
its space indentation collapses under Markdown rendering and the whole
|
|
430
|
+
block displays as one run-on paragraph.
|
|
431
|
+
"""
|
|
432
|
+
if not questions:
|
|
433
|
+
return "**⏸ Paused for input**\n"
|
|
434
|
+
lines: list[str] = ["**⏸ Paused for input**"]
|
|
435
|
+
for qi, q in enumerate(questions, 1):
|
|
436
|
+
header = str(q.get("header") or "").strip()
|
|
437
|
+
question = str(q.get("question") or "").strip()
|
|
438
|
+
label = f"[{header}] {question}" if header else question
|
|
439
|
+
prefix = f"Q{qi}: " if len(questions) > 1 else ""
|
|
440
|
+
lines.append(f"\n**{prefix}{label}**")
|
|
441
|
+
options = q.get("options")
|
|
442
|
+
if isinstance(options, list):
|
|
443
|
+
for opt in options:
|
|
444
|
+
if not isinstance(opt, dict):
|
|
445
|
+
continue
|
|
446
|
+
opt_label = str(opt.get("label") or "").strip()
|
|
447
|
+
desc = str(opt.get("description") or "").strip()
|
|
448
|
+
marker = " ★" if "(Recommended)" in opt_label or "Recommended" in opt_label else ""
|
|
449
|
+
if desc:
|
|
450
|
+
lines.append(f"- **{opt_label}{marker}** — {desc}")
|
|
451
|
+
else:
|
|
452
|
+
lines.append(f"- **{opt_label}{marker}**")
|
|
453
|
+
return "\n".join(lines) + "\n"
|
|
454
|
+
|
|
455
|
+
|
|
423
456
|
async def stream_query(
|
|
424
457
|
agent: SootheNanoAgent,
|
|
425
458
|
query: str,
|
|
@@ -66,10 +66,12 @@ class AgentAdapterBase:
|
|
|
66
66
|
|
|
67
67
|
Manages one agent instance per pool worker: lazily built on first use,
|
|
68
68
|
bound to a workspace's sandbox when a ``sandbox_manager`` is present
|
|
69
|
-
(rebuilt when the worker switches workspaces
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
(rebuilt when the worker switches workspaces *or* that workspace's sandbox
|
|
70
|
+
is replaced — the graph's backend is pinned to a sandbox id, not a path),
|
|
71
|
+
and recycled after a failed or cancelled run — cancellation can interrupt
|
|
72
|
+
middleware cleanup and leave mutable graph state (for example
|
|
73
|
+
edit-coalescing buffers) incomplete, so that graph is never reused for
|
|
74
|
+
another request.
|
|
73
75
|
|
|
74
76
|
Thread state, by contrast, is *not* worker-local: when
|
|
75
77
|
``checkpointer_policy`` is enabled the adapter opens one checkpointer on
|
|
@@ -100,6 +102,8 @@ class AgentAdapterBase:
|
|
|
100
102
|
self._checkpointer_cm: Any | None = None
|
|
101
103
|
self._checkpointer_failed = False
|
|
102
104
|
self._agent_workspace: str | None = None
|
|
105
|
+
#: Sandbox id the cached agent's backend is bound to (``None`` off-sandbox).
|
|
106
|
+
self._agent_sandbox_id: str | None = None
|
|
103
107
|
self._active = False
|
|
104
108
|
self._tainted = False
|
|
105
109
|
self._generation = 1 if agent is not None else 0
|
|
@@ -148,20 +152,53 @@ class AgentAdapterBase:
|
|
|
148
152
|
|
|
149
153
|
async def _ensure_agent(self, *, workspace: str | None = None) -> Any:
|
|
150
154
|
"""Async ensure: open the checkpointer before a config-driven build."""
|
|
151
|
-
|
|
152
|
-
self._sandbox_manager is not None and self._agent_workspace != workspace
|
|
153
|
-
)
|
|
154
|
-
if needs_build and self._agent_factory is None:
|
|
155
|
+
if self._agent_needs_rebuild(workspace) and self._agent_factory is None:
|
|
155
156
|
await self._ensure_checkpointer()
|
|
156
157
|
return self._ensure(workspace=workspace)
|
|
157
158
|
|
|
159
|
+
def _live_sandbox_id(self, workspace: str | None) -> str | None:
|
|
160
|
+
"""Id of the guest the manager currently binds to ``workspace``."""
|
|
161
|
+
if self._sandbox_manager is None or workspace is None:
|
|
162
|
+
return None
|
|
163
|
+
return self._sandbox_manager.sandbox_id_for(workspace)
|
|
164
|
+
|
|
165
|
+
def _agent_needs_rebuild(self, workspace: str | None) -> bool:
|
|
166
|
+
"""True when this worker must build a new agent for ``workspace``.
|
|
167
|
+
|
|
168
|
+
Without a sandbox manager the built agent is workspace-agnostic, so an
|
|
169
|
+
existing instance is reused. With one, the graph holds an
|
|
170
|
+
``ArcBoxBackend`` bound at build time: either a workspace switch or a
|
|
171
|
+
replacement of *that workspace's* sandbox (idle recycle, dead VM,
|
|
172
|
+
eviction) leaves it pointing at a guest that no longer exists, where
|
|
173
|
+
every file and shell tool fails with ``VM not found``. The path alone
|
|
174
|
+
cannot detect the second case — it stays the same across generations —
|
|
175
|
+
so the live sandbox id is part of the cache key.
|
|
176
|
+
"""
|
|
177
|
+
if self._agent is None:
|
|
178
|
+
return True
|
|
179
|
+
if self._sandbox_manager is None:
|
|
180
|
+
return False
|
|
181
|
+
return (
|
|
182
|
+
self._agent_workspace != workspace
|
|
183
|
+
or self._agent_sandbox_id != self._live_sandbox_id(workspace)
|
|
184
|
+
)
|
|
185
|
+
|
|
158
186
|
def _ensure(self, *, workspace: str | None = None) -> Any:
|
|
187
|
+
if not self._agent_needs_rebuild(workspace):
|
|
188
|
+
assert self._agent is not None
|
|
189
|
+
return self._agent
|
|
159
190
|
if self._agent is not None:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
191
|
+
# Bound to a workspace/sandbox generation that is no longer current.
|
|
192
|
+
logger.info(
|
|
193
|
+
"%s: rebuilding agent for %s (sandbox %s -> %s)",
|
|
194
|
+
self.label,
|
|
195
|
+
workspace,
|
|
196
|
+
self._agent_sandbox_id,
|
|
197
|
+
self._live_sandbox_id(workspace),
|
|
198
|
+
)
|
|
199
|
+
self._agent = None
|
|
200
|
+
self._agent_sandbox_id = None
|
|
201
|
+
live_sandbox_id = self._live_sandbox_id(workspace)
|
|
165
202
|
if self._agent_factory is not None:
|
|
166
203
|
self._agent = self._agent_factory()
|
|
167
204
|
else:
|
|
@@ -170,6 +207,7 @@ class AgentAdapterBase:
|
|
|
170
207
|
sandbox_backend = self._sandbox_manager.backend_for(workspace)
|
|
171
208
|
self._agent = self._build(sandbox_backend=sandbox_backend)
|
|
172
209
|
self._agent_workspace = workspace
|
|
210
|
+
self._agent_sandbox_id = live_sandbox_id
|
|
173
211
|
self._generation += 1
|
|
174
212
|
return self._agent
|
|
175
213
|
|
|
@@ -216,11 +254,13 @@ class AgentAdapterBase:
|
|
|
216
254
|
if self._tainted:
|
|
217
255
|
self._agent = None
|
|
218
256
|
self._agent_workspace = None
|
|
257
|
+
self._agent_sandbox_id = None
|
|
219
258
|
self._tainted = False
|
|
220
259
|
|
|
221
260
|
async def cleanup(self) -> None:
|
|
222
261
|
self._agent = None
|
|
223
262
|
self._agent_workspace = None
|
|
263
|
+
self._agent_sandbox_id = None
|
|
224
264
|
self._tainted = False
|
|
225
265
|
self._active = False
|
|
226
266
|
cm, self._checkpointer_cm = self._checkpointer_cm, None
|