Addresses review findings before Slice 5 sandbox work.
Resolver primitives:
- resolveOutcome maps an attempt outcome to retry-or-terminal; removed the
no-op isTerminalClassification.
- WORK_STATUS_FOR_OUTCOME preserves real terminal Work status instead of
collapsing everything back to ready.
- Lifecycle: completed is terminal, cancelled reopens only to ready, and the
invalid awaiting-design-approval -> awaiting-definition-approval reopening
is gone.
Execution (workExecution.ts):
- finishAttempt replaces completeAttempt: auto-retries within the kit budget
and settles Work at the real classification (Succeeded->completed,
PermanentFailure/BudgetExhausted->failed, NeedsInput->needs-input,
Blocked->blocked). No Work stays silently runnable after a failure.
- startSimulatedExecution validates sliceId against the current approved
Design (foreign ids rejected; defaults to the first slice) and advances
slice status running -> completed/ready.
- retrySimulatedExecution is now an explicit user restart with a retryable-
state guard, not the automatic resolver path.
- reconcileExpiredAttempts is bounded: a dead lease terminates as Blocked,
resumes within budget or settles the Run/Work so nothing runs forever.
- crons.ts runs the reconciler every 30s, string-referenced so it does not
depend on the stale codegen.
Planning (workPlanning.ts):
- saveDefinition/saveDesign reject revision while a Run is executing.
- submitQuestion validates and persists a durable workQuestions row instead
of discarding planner questions.
- Design revision invalidates active design approvals.
Verification: primitives 69 tests (+4), backend 22 tests (+10) covering
retry/cancel/failure/restart, slice validation, reconciliation, the
revision guard, and question persistence. Root typecheck, web+agents build,
and the configured Ultracite gate (53 files) all pass.
package.json: the recurring check gate now covers workExecution, workPlanning,
crons, resolver, and work-lifecycle.
Domain primitives: versioned WorkDefinition/DesignPacket with risk, questions,
and approval gates; lifecycle transition table with invalidation rules;
Resolver (Run/Attempt/outcomes/CodingKitV0); provider-neutral HarnessRuntime
with normalized events and a deterministic FakeHarnessLive that always reaches
a terminal classification and never claims implementation.
Convex durable model: workDefinitions, workQuestions, workApprovals,
designPackets, workSlices, workRuns, workAttempts, workAttemptEvents tables;
broadened works.status union; generalized workEvents with optional signalId,
referenceId, and payloadJson. Authenticated commands for definition request/
save/revise/approve, question answer/withdraw, design save/revise/approve, and
simulated execution start/cancel/retry with leased attempts, checkpointed
events, and expired-lease reconciliation.
Private work-planner FLUE agent with proposal-only tools (definition, design,
question). Convex validates and stores every proposal; FLUE never approves or
advances Work.
Expanded Work card with Outcome, Design, and Build sections wired to the new
mutations. Slice 1 conversation and provenance preserved.
- Archive dormant apps (daemon, desktop, native, tui) and superseded
packages/server into repos/ for reference
- Archive 21 dead web components (chat page shell, work-os cluster, strays)
into repos/web/; keep reused message-rendering primitives in components/chat
- Merge @code/work-os into @code/primitives; work.ts absorbed via ./work
subpath and barrel export; update all four importers
- Add docs/futures.md tracking audio/video (blocked at Flue + provider),
web layout cleanup, and message rendering quality
- Repoint dev:zopu script to @code/agents (the live Flue server)
- Note repos/ reference-code convention in AGENTS.md
A vertical slice to bootstrap the product loop on the canonical zopu-code repo.
Primitives (packages/primitives, all tested + lint/type clean):
- GitRemoteRuntime: Gitea REST client (createIssue, createBranch,
createPullRequest, listIssues, getIssue) as an Effect context service with
a fetch-backed transport. Live-verified against puter/zopu-code.
- GitLocalRuntime: clone, addWorktree, commit, push, currentBranch,
setRemoteUrl over a pluggable Shell (host subprocess now, AgentOS VM exec
later). Tested against real temp git repos.
- agent-os: Codex support via @agentos-software/codex-cli — codexSoftware
bundle (codex+git), makeCodexAgentOsConfig(), codexSessionEnv() for the
OpenAI base URL/key.
Env:
- GITEA_URL/GITEA_TOKEN wired into convex.ts and convex.config.ts.
- .env.example documents the self-hosted git section.
Agents (packages/agents):
- zopu-dev: development agent that creates issues on the canonical repo and
starts autonomous work runs. git-remote tools (create/list/branch/PR) wired
to GitRemoteRuntime; start_workflow tool enqueues a work run.
- flue run zopu-dev verified live (lists/creates issues on real Gitea).
Backend (packages/backend):
- workflows.startIssueWork mutation: fetches a Gitea issue server-side and
admits a queued work run. The Codex VM spawn body is the next step.
Extend the smoke harness with Orb execution-plane preflight probes
(Docker, OpenCode, Gitea reachability/creds/PR-creation, repository
writable) that report BLOCKED with exact reasons when dependencies are
absent. Add scripts/orb-project-run.ts as the missing connection point
between OrbProjectManager and the Orb/Git lifecycle. Write
docs/DOGFOOD_V0.md covering architecture, startup order, env vars,
local/server flows, demo procedure, health checks, failure recovery,
cleanup, known limitations, and next milestones.
The smoke harness now runs 18 preflight checks across control-plane and
Orb execution-plane layers, all with stable markers and sanitized JSON
reports. The orb:run script (ORB_RUN=1) drives one issue through the
full Orb project-manager lifecycle.
Add a thin orchestration layer that drives the existing Orb runtime to
progress issue-scoped work. OrbProjectManager creates/resumes Orb runs,
assembles context packs, projects OrbEvents into durable project events,
detects needs-input and work-complete conditions, forwards follow-up
messages, and runs the Git/Gitea lifecycle on completion.
Port interfaces (OrbAdapter, OrbRunPort, GitLifecyclePort) make the
orchestrator testable with fakes. The real adapters wrap OrbRuntime and
runPostRunGiteaLifecycle without reimplementing Docker, AgentOS, or
OpenCode.
Idempotency: duplicate starts reuse active runs, duplicate completion
returns cached results, cancel is idempotent. Never auto-merges.
88 tests pass (19 orchestration + 12 event-mapping + orb suite), 2 live
tests skipped; lint and type checks clean.
The in-process DockerSandboxClient could never satisfy AgentOS 0.2.10's
sandbox serialization contract: AgentOS serializes sandbox mounts through
getSerializableClientConfig, which reads client.baseUrl and passes it to the
sidecar. An in-process object has no network endpoint.
Replace the custom adapter with the supported boundary: SandboxAgent.start
with sandbox-agent/docker, which starts a sandbox-agent server inside a
Docker container with a dynamically mapped host port and returns a
SandboxAgent client whose baseUrl both the main process and the sidecar
subprocess reach over 127.0.0.1. Dispose calls destroySandbox so no
containers are left behind.
Remove 700+ lines of dead DockerSandboxClient infrastructure (PID tracking,
log files, signal handling) now handled natively by the sandbox-agent
server. Add a sqlite_file database descriptor to AgentOs.create so session
storage works. Wrap command execution in sh -c for the SandboxAgent API.
Apply chmod 600 to the real OpenCode config path containing the gateway key.
The real proof with the main .env now passes all three stages:
ORB_STAGE_DOCKER_OK, ORB_STAGE_AGENTOS_OK, ORB_STAGE_MODEL_TURN_OK,
ending ORB_PROOF_PASSED with zero leftover containers.
Update README to describe the real SandboxAgent + Docker topology and
remove the misleading 'missing sidecar' limitation.
Fix detached process startup in docker-sandbox.ts: capture the real process
group PID via setsid, redirect stdout/stderr to durable log files, refresh
status and exit code after natural completion, make getProcessLogs return real
logs, and implement bounded TERM/KILL cleanup with no leftover containers.
Switch the adapter to node:child_process (supported by Bun) so tests execute
under vitest, add -i to docker exec so staged files reach cat, and assert the
launcher is nonempty before launch.
Ensure Orb creation disposes the VM before its sandbox on partial failure
(AgentOS creation or OpenCode linking), and dispose the VM before its sandbox
in OrbHandle.dispose. Fix the Effect state callbacks (tapError/sync/asVoid) and
prepareRepository branch/base shell quoting.
Split normalizeSessionEvent to stay under the complexity limit without
suppressing the rule. Raw outbound prompts stay unchanged; only persisted and
logged event copies are redacted.
Rewrite scripts/orb-proof.ts to emit explicit stable stage markers for Docker,
AgentOS/OpenCode, and the model turn; ORB_GATEWAY_* fall back safely to
AGENT_MODEL_* without printing secrets; an unreachable gateway is BLOCKED not
passed; container removal is verified after disposal.
Strengthen tests: drop the any-failure createOrb test and add focused Docker
lifecycle coverage (PID, logs, status, exit code, stop/kill, removal) guarded
only when Docker is unavailable.
Add Convex backend tests for listLinkedSignalsForProject covering:
- Per-issue grouping with correct source counts
- Project membership/authz (denies non-member access)
- Cross-project signal suppression (project1 cannot see project2 signals)
- Multi-source signal source-count accuracy
- Empty project returns empty record
- Issues without linked signals are omitted from result
Make linked signals deterministically ordered newest-first within
each issue group via toSorted(createdAt descending).
Replace the deliberate signalCount of 0 with authenticated linked-Signal
data from the Lane A signalIssueAttachments relation.
- Add listLinkedSignalsForProject query (user-authenticated via
requireProjectMember) returning issue-grouped LinkedSignalView data
using the existing by_issue index. One query serves both card counts
and expanded detail.
- Update work-unit-projection builders to accept linkedSignals param;
LinkedSignal type with signalId, title, summary, sourceCount, createdAt.
- Feed real attachment data into collapsed card signalCount and expanded
detail linked-signal list with provenance (source count, relative time).
- Add signal.attached event handler to activity timeline projection.
- 32 tests including 4 linked-signal projection tests, 1 signal.attached
activity mapping test, and cross-issue signal isolation test proving
issue A cannot inherit issue B's linked signals.
Lane C implementation of the Zopu Web Work OS for dogfooding:
- Work Unit card/detail projections built from per-issue events only
(artifact counts from distinct artifact.updated paths, PR from
gitea events, activity timeline, agent summaries). Signal linkage
is explicitly 0/unavailable until Lane A relations are integrated.
- Persistent composer with Project and Work Unit mode switching.
Project mode sends to the global Zopu agent; Work Unit mode sends
to the issue-scoped project-manager agent identity.
- Collapsed Work Unit cards show title, summary, signal/step/artifact
counts, current activity, PR indicator, needs-input indicator.
- Expanded Work Unit detail shows objective, timeline, artifacts,
PR with directly usable link, needs-input alert, start action.
- Project-level Signals panel in the sidebar (not attributed to any
issue until a signal-to-issue relation exists).
- Dark theme with calm, Apple-like visual direction.
- 21 projection tests including 5 cross-issue isolation tests and
2 signal isolation tests proving one issue cannot inherit
another issue's artifacts, PR, summary, or project signals.
- Mobile-responsive: detail overlay on mobile, sidebar on desktop.
No backend or schema changes. Uses existing Convex contracts and
Flue agent transport throughout.
Add the complete project-scoped Zopu chat, Signal extraction, and
work-routing loop:
Backend (signalRouting.ts):
- Agent-token-gated functions for the full routing cycle: list evidence,
create signal, list signals, list active issues, attach signal to
issue, create issue from signal, begin issue, get project context,
list projects
- attachSignalToIssue requires signal.projectId to exist and equal
issue.projectId (org equality alone is insufficient)
- createIssueFromSignal is idempotent: queries existing attachments
before creating, returns created/reused flag
- Duplicate attach retries return existing relation without emitting a
duplicate event
- All functions deny-by-default with agent token validation
Schema:
- Add signalIssueAttachments table with indexes by signal, issue, and
the composite (signalId, issueId) for idempotent lookups
Agent tools (tools/signals.ts):
- Nine Flue tool definitions bound to the organization-scoped agent
instance ID, calling the agent-gated Convex functions
- Organization ID never accepted as free input; always from the bound
instance
Zopu agent (zopu.ts):
- Comprehensive routing-loop instructions: assess actionability, identify
project, create signal only when actionable, route via attach or create,
explain outcome, optionally begin
- Wiring of routing tools into the agent definition
Tests (signalRouting.test.ts):
- Authentication: invalid token rejected on every function
- Project scoping: evidence, signals, issues scoped correctly
- Idempotency: duplicate attach, duplicate create-from-signal, duplicate
signal creation
- Attach vs create: both paths verified
- Cross-project rejection: signal and issue must be in same project
- Org-scoped signal attach rejected (no projectId)
- Provenance: exact raw text preserved through routing loop
- Begin issue: transition and auth verification
Remove the OutOfOrderSource constraint from composeSignal. The agent's
selection order is authoritative for source ordinals; timestamps remain
as per-source provenance metadata. This resolves the contract conflict
where reversed message selections (valid agent compositions) were
rejected by the primitive.
Each source still retains its original createdAt, so chronological
provenance is preserved exactly. The sourceKey includes ordered message
IDs, so different orderings produce distinct signals with deterministic
idempotency.
Coverage added:
- Primitive: non-chronological order accepted, timestamps preserved
- Backend: reversed selection preserves ordinals and exact raw text
- Fix all Ultracite lint errors on changed source files (0 remaining)
- Remove project.test.ts and use-personal-organization.test.ts (to be
rewritten when implementation stabilizes)
- Replace .at(-1) with index access for backend tsconfig ES target compat
- Rename generator functions to avoid no-shadow conflicts
- Refactor nested ternaries into helper functions
- Fix unused imports
Auth:
- Add useWebAuth hook as the single web-facing auth interface with four
states (loading/unauthenticated/authenticated/inconsistent)
- Normalize getCurrentUser to return {id: tokenIdentifier, name, email}
- Rename requireOwnerId to requireAuthUserId across all backend callers
- Set expectAuth:true on ConvexReactClient singleton
- Migrate _app, _auth, user-menu, use-personal-organization to useWebAuth
- Remove unused @code/auth/server export (zero callers confirmed)
Primitives:
- Add packages/primitives/src/project.ts with Effect v4 Project domain:
URL normalization, six canonical context kinds, strict remote decoding,
context write decisioning with revision tracking, and no-op detection
- Define PublicGit and ProjectStore replaceable ports
- Define ProjectApplication service orchestrating domain+ports with
deterministic error mapping
- Add comprehensive test suite (28 tests) with in-memory store and fake
PublicGit covering normalization, seeds, idempotency, tenant isolation,
last-write-wins, and typed error mapping