Skip to content

feat: add phase 2 owner substrate - #79

Merged
delete-cloud merged 8 commits into
mainfrom
phase2/owner-substrate-mainline
Apr 21, 2026
Merged

delete-cloud merged 8 commits into
mainfrom
phase2/owner-substrate-mainline

Conversation

@delete-cloud

@delete-cloud delete-cloud commented Apr 20, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • add a PostgreSQL-backed session owner lease store and typed owner record abstraction for the ADR-0013 Phase 2 substrate
  • guard session runtime entry points with owner/fencing checks and tighten the owner validation path to use typed owner records only
  • carry forward the review hardening for owner metadata validation and substrate tests without pulling the later HTTP/failover branch history into main

Verification

  • uv run pytest tests/ui/test_session_owner_store.py tests/ui/test_session_manager_owner_checks.py -v
  • uv run pytest tests/agentkit/storage/test_pg.py -k "owner or lease" -v

Summary by CodeRabbit

  • New Features

    • Distributed session ownership with lease-based exclusive access and fencing tokens.
    • Persistent, database-backed owner store for session ownership.
    • Session manager now enforces owner/fencing checks on start/restore/close/checkpoint and other lifecycle operations.
    • HTTP endpoints now return 409 Conflict for session ownership conflicts.
  • Tests

    • Expanded test coverage for owner-store behaviors, DB test double, manager enforcement, concurrency, and token/lease semantics.

delete-cloud and others added 3 commits April 21, 2026 00:30
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Address review feedback around owner-store initialization, owner record typing, and test fixture protocol alignment.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Copilot AI review requested due to automatic review settings April 20, 2026 16:31
@coderabbitai

coderabbitai Bot commented Apr 20, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds lease-based session ownership and fencing: a PostgreSQL-backed PGSessionOwnerStore, a SessionOwnerStore abstraction and protocols, owner validation integrated into SessionManager (precondition checks on many operations), and tests for backend, store, and manager owner-validation behavior.

Changes

Cohort / File(s) Summary
PG storage backend
coding-agent/src/agentkit/storage/pg.py
New PGSessionOwnerStore with schema creation and lease/fencing lifecycle methods: acquire() (UPSERT if no unexpired lease), renew() (conditional extend when owner/token/lease match), release() (delete when owner/token match), and get_owner() (only returns unexpired lease).
Owner store abstraction & implementation
coding-agent/src/coding_agent/ui/session_owner_store.py
New SessionOwnerRecord dataclass, SessionOwnerBackend and SessionOwnerStoreProtocol async Protocols, SessionOwnerStore wrapper that normalizes backend returns and auto-wires PG backend when needed; adds SessionOwnershipConflictError.
SessionManager owner fencing
coding-agent/src/coding_agent/ui/session_manager.py
Constructor accepts owner_store, owner_id, fencing_token (validated as a matched set) and adds async _assert_owner(session_id) invoked at many lifecycle entry points to validate existence, unexpired lease (UTC), and matching owner/fencing token.
HTTP error mapping
coding-agent/src/coding_agent/ui/http_server.py
Endpoints now catch SessionOwnershipConflictError and return HTTP 409 with the conflict message for ownership-related failures.
PG test double & tests
coding-agent/tests/agentkit/storage/test_pg.py
Extended FakePool to emulate session_owners semantics (insert/update/delete respecting lease expiry and fencing tokens), updated fetchrow, and added TestPGSessionOwnerStore tests for acquire/renew/release/get_owner.
Owner store unit tests
coding-agent/tests/ui/test_session_owner_store.py
Added FakePGOwnerStore fixture and tests for acquire conflicts, renew rejecting stale tokens, release rejecting wrong owners, and typed get_owner result.
SessionManager owner-check tests
coding-agent/tests/ui/test_session_manager_owner_checks.py
New tests with FakeOwnerStore/FakeCheckpointStore asserting constructor validation and that SessionManager rejects operations when ownership is stale or expired (including a concurrency revalidation case).

Sequence Diagram

sequenceDiagram
    participant Client
    participant SessionManager
    participant OwnerStore as SessionOwnerStore
    participant Backend as PGSessionOwnerStore
    participant DB as PostgreSQL<br/>session_owners

    Client->>SessionManager: init(owner_id, fencing_token)
    SessionManager->>SessionManager: validate owner metadata

    rect rgba(100,150,200,0.5)
        Note over Client,DB: Acquire ownership
        Client->>SessionManager: run_agent(session_id)
        SessionManager->>OwnerStore: acquire(session_id, owner_id, lease_seconds, fencing_token)
        OwnerStore->>Backend: acquire(...)
        Backend->>DB: UPSERT session_owners (if no unexpired lease)
        DB-->>Backend: success / conflict
        Backend-->>OwnerStore: bool
        OwnerStore-->>SessionManager: bool
    end

    rect rgba(150,100,200,0.5)
        Note over Client,DB: Ownership check
        SessionManager->>OwnerStore: get_owner(session_id)
        OwnerStore->>Backend: get_owner(session_id)
        Backend->>DB: SELECT owner_id, lease_expires_at, fencing_token
        DB-->>Backend: record (if lease unexpired)
        Backend-->>OwnerStore: SessionOwnerRecord
        OwnerStore-->>SessionManager: SessionOwnerRecord
        SessionManager->>SessionManager: _assert_owner() validates match
    end

    rect rgba(200,150,100,0.5)
        Note over Client,DB: Renew lease
        SessionManager->>OwnerStore: renew(session_id, owner_id, lease_seconds, new_fencing_token, current_fencing_token)
        OwnerStore->>Backend: renew(...)
        Backend->>DB: UPDATE session_owners (if owner_id, token, lease match)
        DB-->>Backend: success / conflict
        Backend-->>OwnerStore: bool
        OwnerStore-->>SessionManager: bool
    end

    rect rgba(200,100,150,0.5)
        Note over Client,DB: Release ownership
        Client->>SessionManager: close_session(session_id)
        SessionManager->>SessionManager: _assert_owner() final check
        SessionManager->>OwnerStore: release(session_id, owner_id, fencing_token)
        OwnerStore->>Backend: release(...)
        Backend->>DB: DELETE FROM session_owners (if owner_id & token match)
        DB-->>Backend: success / conflict
        Backend-->>OwnerStore: bool
        OwnerStore-->>SessionManager: bool
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I tunneled through leases with token in paw,

I fenced every session and checked the law.
I hop to acquire, renew, and release,
With careful hops, conflicts find peace.
Hooray — owners kept tidy, chaos ceased!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.49% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add phase 2 owner substrate' directly corresponds to the main objective of the changeset, which is adding PostgreSQL-backed session owner lease store and owner record abstractions as part of Phase 2 substrate work.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase2/owner-substrate-mainline

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
coding-agent/tests/ui/test_session_manager_owner_checks.py (1)

19-35: Model live and expired leases separately in this fake.

FakeOwnerStore.acquire() writes lease_expires_at=datetime.now(UTC), so every acquired record is already expired at creation time. That makes the mismatch assertions pass, but it leaves this suite unable to distinguish a valid live lease from an expired one. Store now + lease_seconds here and add one case where owner_id/fencing_token still match but the lease is in the past.

Suggested adjustment
-from datetime import UTC, datetime
+from datetime import UTC, datetime, timedelta
...
         self._owners[session_id] = SessionOwnerRecord(
             owner_id=owner_id,
-            lease_expires_at=datetime.now(UTC),
+            lease_expires_at=datetime.now(UTC) + timedelta(seconds=lease_seconds),
             fencing_token=fencing_token,
         )

Also applies to: 93-164

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coding-agent/tests/ui/test_session_manager_owner_checks.py` around lines 19 -
35, The fake owner store's acquire method sets lease_expires_at to
datetime.now(UTC) causing all leases to be immediately expired; change
FakeOwnerStore.acquire to set lease_expires_at to datetime.now(UTC) +
timedelta(seconds=lease_seconds) (use the SessionOwnerRecord fields owner_id,
lease_expires_at, fencing_token) so live vs expired leases can be distinguished,
and add one test case where owner_id and fencing_token match but
lease_expires_at is in the past to verify expired-lease behavior; also apply the
same lease_expires_at handling to the other fake owner-store methods in the file
that create SessionOwnerRecord (lines referenced in the review) so they
consistently use now + lease_seconds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@coding-agent/src/coding_agent/ui/session_manager.py`:
- Around line 964-966: The ownership check in session_manager methods is
performed before awaiting the local lock, allowing a lease change to slip in
while blocked; move the call to _assert_owner(session_id) so it executes after
acquiring the lock (inside the async with self._lock or the corresponding
turn_lock scope) and immediately before any destructive operations (e.g., before
calling get_session_async, close_session logic, or run_agent's first mutating
step). Update the occurrences around the current calls at get_session_async
(lines ~964) and the similar block at ~1015-1020 to call await
self._assert_owner(session_id) inside the lock context to revalidate ownership
before proceeding.
- Around line 493-510: The _assert_owner method currently only checks owner_id
and fencing_token but must also reject expired leases: after fetching owner =
await self._owner_store.get_owner(session_id) inspect owner.lease_expires_at and
compare to the current time (e.g., datetime.utcnow() or timezone-aware
equivalent); if lease_expires_at is in the past (or missing/None if you treat
that as expired) raise a RuntimeError (e.g., "session lease expired") so stale
SessionOwnerRecord entries are not accepted—update _assert_owner to perform this
expiry check before validating owner_id/fencing_token (references:
_assert_owner, self._owner_store.get_owner, SessionOwnerRecord.lease_expires_at,
owner.owner_id, owner.fencing_token).

In `@coding-agent/tests/agentkit/storage/test_pg.py`:
- Around line 110-122: The FakePool test double is storing raw lease payloads
and returning expired rows unfiltered, diverging from the real
PGSessionOwnerStore behavior; update FakePool’s insert/update branches that
touch session_owners (the block handling "INSERT INTO session_owners" and the
similar blocks at 123-143 and 185-195) to compute and store a proper
lease_expires_at timestamp (e.g., current time + lease_seconds or a comparable
numeric expiry) instead of the raw third argument, and ensure the read path (the
logic used by get_owner in tests) filters out rows whose stored lease_expires_at
is expired (past timestamp) so FakePool enforces the same expiry suppression and
typing as PGSessionOwnerStore.

---

Nitpick comments:
In `@coding-agent/tests/ui/test_session_manager_owner_checks.py`:
- Around line 19-35: The fake owner store's acquire method sets lease_expires_at
to datetime.now(UTC) causing all leases to be immediately expired; change
FakeOwnerStore.acquire to set lease_expires_at to datetime.now(UTC) +
timedelta(seconds=lease_seconds) (use the SessionOwnerRecord fields owner_id,
lease_expires_at, fencing_token) so live vs expired leases can be distinguished,
and add one test case where owner_id and fencing_token match but
lease_expires_at is in the past to verify expired-lease behavior; also apply the
same lease_expires_at handling to the other fake owner-store methods in the file
that create SessionOwnerRecord (lines referenced in the review) so they
consistently use now + lease_seconds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a516de2-5a66-407e-a4d9-bc24c5db6148

📥 Commits

Reviewing files that changed from the base of the PR and between 7aa5d0d and 693b864.

📒 Files selected for processing (6)
  • coding-agent/src/agentkit/storage/pg.py
  • coding-agent/src/coding_agent/ui/session_manager.py
  • coding-agent/src/coding_agent/ui/session_owner_store.py
  • coding-agent/tests/agentkit/storage/test_pg.py
  • coding-agent/tests/ui/test_session_manager_owner_checks.py
  • coding-agent/tests/ui/test_session_owner_store.py

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Copilot AI review requested due to automatic review settings April 21, 2026 01:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@coding-agent/src/coding_agent/ui/session_manager.py`:
- Around line 385-391: The constructor currently only rejects when owner_store
is set but owner_id or fencing_token are missing; add the inverse validation to
also reject when owner_store is None but either owner_id or fencing_token is
provided. In the SessionManager constructor (the code handling owner_store,
owner_id, and fencing_token), raise a ValueError with a clear message like
"owner_store must be provided when owner_id or fencing_token is set" if
owner_store is None and (owner_id is not None or fencing_token is not None), so
ownership cannot be silently disabled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 71057c1d-ed62-474f-9dbf-757b1782b21c

📥 Commits

Reviewing files that changed from the base of the PR and between 693b864 and 2ce5a98.

📒 Files selected for processing (3)
  • coding-agent/src/coding_agent/ui/session_manager.py
  • coding-agent/tests/agentkit/storage/test_pg.py
  • coding-agent/tests/ui/test_session_manager_owner_checks.py
✅ Files skipped from review due to trivial changes (1)
  • coding-agent/tests/ui/test_session_manager_owner_checks.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the Phase 2 “owner substrate” for multi-instance safety by introducing a PostgreSQL-backed session ownership/lease store and applying owner/fencing validation to key session runtime operations.

Changes:

  • Introduces PGSessionOwnerStore plus a typed SessionOwnerRecord wrapper (SessionOwnerStore) for validating/normalizing owner payloads.
  • Adds owner/fencing validation hooks to SessionManager runtime entry points (run/close/checkpoint/restore paths).
  • Adds targeted unit tests for the owner store and owner gating behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
coding-agent/src/agentkit/storage/pg.py Adds PGSessionOwnerStore with schema + acquire/renew/release/get operations backed by Postgres.
coding-agent/src/coding_agent/ui/session_owner_store.py Adds typed owner record abstraction and a wrapper store that normalizes backend payloads to SessionOwnerRecord.
coding-agent/src/coding_agent/ui/session_manager.py Adds owner/fencing parameters + _assert_owner() and integrates owner checks into several runtime entry points.
coding-agent/tests/agentkit/storage/test_pg.py Extends PG fakes and adds coverage for PGSessionOwnerStore.
coding-agent/tests/ui/test_session_owner_store.py Adds tests validating typed owner record behavior for the UI-layer owner store wrapper.
coding-agent/tests/ui/test_session_manager_owner_checks.py Adds tests ensuring stale/expired ownership is rejected across key SessionManager operations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +493 to +513
async def _assert_owner(self, session_id: str) -> None:
if self._owner_store is None:
return
if self._owner_id is None or self._fencing_token is None:
raise RuntimeError("stale owner or fencing token rejected")

owner = await self._owner_store.get_owner(session_id)
if owner is None:
raise RuntimeError("session has no owner")
if owner.lease_expires_at <= datetime.now(UTC):
raise RuntimeError("session owner lease expired")

current_owner_id = owner.owner_id
current_fencing_token = owner.fencing_token

if (
current_owner_id != self._owner_id
or current_fencing_token != self._fencing_token
):
raise RuntimeError("stale owner or fencing token rejected")

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Owner/fencing enforcement is only applied on a subset of mutating entry points. shutdown_session_runtime() and ensure_session_runtime() (and also submit_approval() / wait_for_http_approval()) mutate/persist session state but currently do not call _assert_owner(), which allows a non-owner or stale instance to clear turn_in_progress, alter tape_id, or record approvals despite failing run_agent/checkpoint/close checks. Add await self._assert_owner(session_id) early in those methods (ideally under the same lock/turn_lock used for the mutation) to make fencing consistent.

Copilot uses AI. Check for mistakes.
return _WireConsumer(session.wire, _request_approval)

async def _restore_checkpoint(self, session: Session, checkpoint_id: str) -> None:
await self._assert_owner(session.id)

Copilot AI Apr 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restore_checkpoint() already calls _assert_owner(session_id) under the turn lock, and _restore_checkpoint() now calls _assert_owner(session.id) again. This duplicates an owner-store read and can also create a confusing failure mode where the first check passes but the second fails if the lease expires between them. Prefer enforcing ownership in exactly one place (either the public method before calling _restore_checkpoint, or inside _restore_checkpoint and remove the outer call).

Suggested change
await self._assert_owner(session.id)

Copilot uses AI. Check for mistakes.
delete-cloud and others added 2 commits April 21, 2026 09:17
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
coding-agent/src/coding_agent/ui/session_manager.py (2)

1166-1175: ⚠️ Potential issue | 🟠 Major

Preserve the existing “Session not found” behavior here.

Calling _assert_owner() before the existence check makes the ApprovalResponse(..., feedback="Session not found") branch unreachable whenever fencing is enabled and the owner row is absent. Missing sessions now raise RuntimeError("session has no owner") instead of returning the method’s current fallback result.

Suggested fix
     async def wait_for_http_approval(
         self,
         session_id: str,
         approval_req: ApprovalRequest,
         timeout_seconds: float,
     ) -> ApprovalResponse:
-        await self._assert_owner(session_id)
-        if not await self.has_session_async(session_id):
+        try:
+            session = await self.get_session_async(session_id)
+        except KeyError:
             return ApprovalResponse(
                 session_id=session_id,
                 request_id=approval_req.request_id,
                 approved=False,
                 feedback="Session not found",
             )
-
-        session = await self.get_session_async(session_id)
+        await self._assert_owner(session_id)
         if not session.turn_in_progress:
             return ApprovalResponse(
                 session_id=session_id,
                 request_id=approval_req.request_id,
                 approved=False,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@coding-agent/src/coding_agent/ui/session_manager.py` around lines 1166 -
1175, The current logic calls _assert_owner(session_id) before checking
existence, causing a RuntimeError when a session has no owner and making the
"Session not found" ApprovalResponse unreachable; swap the checks so you call
has_session_async(session_id) first and return the ApprovalResponse(...) if
false, and only then call _assert_owner(session_id) and proceed to
get_session_async(session_id) — update the block around _assert_owner,
has_session_async, get_session_async and the ApprovalResponse/approval_req usage
accordingly.

1271-1307: ⚠️ Potential issue | 🟠 Major

ensure_session_runtime() is still only point-in-time fenced.

This method validates ownership once, then awaits _restore_tape() and adapter.initialize() before writing runtime_pipeline, runtime_ctx, runtime_adapter, and tape_id. If the lease changes hands during either await, the stale owner still installs a runtime and persists it. The other destructive entry points were moved behind local locking; this path needs the same revalidation/serialization around the commit step.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@coding-agent/src/coding_agent/ui/session_manager.py`:
- Around line 969-970: The new RuntimeError from _assert_owner should not
surface as a 500 in the HTTP layer; in close_session() wrap the await
self._assert_owner(session_id) call in a try/except that catches RuntimeError
and re-raises a KeyError (or another exception already mapped to a
client-visible conflict) with the original message (e.g. "raise KeyError(str(e))
from e") so the existing http_server handling will map the stale-owner/lease
conflict to the proper client response instead of an internal server error.

---

Outside diff comments:
In `@coding-agent/src/coding_agent/ui/session_manager.py`:
- Around line 1166-1175: The current logic calls _assert_owner(session_id)
before checking existence, causing a RuntimeError when a session has no owner
and making the "Session not found" ApprovalResponse unreachable; swap the checks
so you call has_session_async(session_id) first and return the
ApprovalResponse(...) if false, and only then call _assert_owner(session_id) and
proceed to get_session_async(session_id) — update the block around
_assert_owner, has_session_async, get_session_async and the
ApprovalResponse/approval_req usage accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a3ee12e6-9c4b-474f-ba8a-8de518247c31

📥 Commits

Reviewing files that changed from the base of the PR and between 2dabef8 and cd6b72c.

📒 Files selected for processing (2)
  • coding-agent/src/coding_agent/ui/session_manager.py
  • coding-agent/tests/ui/test_session_manager_owner_checks.py

delete-cloud and others added 2 commits April 21, 2026 09:47
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@delete-cloud
delete-cloud merged commit e0e7a3b into main Apr 21, 2026
1 check passed
@delete-cloud
delete-cloud deleted the phase2/owner-substrate-mainline branch April 21, 2026 02:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants