Skip to content

Repository files navigation

Loophony

Loophony is a small, Linear-driven 24/7 agent orchestrator built on OpenAI Symphony. You define one durable objective on a Linear project, and Loophony repeatedly gives Codex one bounded Linear issue at a time. Linear is the human-facing control plane; local SQLite preserves issue checkpoints, while optional Onyx v4 and OpenSearch 3.6 hybrid search let Codex answer natural-language questions across sessions.

Warning

Loophony is an experimental preview for trusted local environments. It never enables live trading, spending, or secret entry through prompts.

Operating contract

  • One Loophony loop equals one Linear issue and one fresh Codex execution session.
  • A completed loop immediately hands off to the next eligible issue; the 30-second idle heartbeat only discovers externally created work and reconciles missed state changes.
  • Exactly one top-level Codex worker session and one internal pending dispatch slot are allowed globally. Linear may contain multiple aligned Todo issues, but only one executable issue may be In Progress.
  • A dedicated [Goal Planner][SC-XX] session runs on Sol medium. It first reviews the prior issue's acceptance claims against terminal evidence, then compares the root goal and a compact Todo/Waiting snapshot before reusing existing work or authoring one bounded execution issue. Successor creation and selection fail closed until the source review is persisted. Planner sessions are capped at two turns and avoid full backlog/repository scans. Workers and exploration sessions only leave proposals; they cannot create Linear issues.
  • Goal-Approved execution issues carry both durable loophony-handoff:v2 routing and loophony-goal-approval:v1 provenance markers. Mechanical work, tiny UI work, and fully specified normal bug fixes or scoped features use GPT-5.3-Codex-Spark medium so its separate usage pool absorbs bounded execution. Failed Spark attempts use Luna xhigh for subsequent retries. Unclear multi-area exploration uses Terra medium, and complex architecture/auth/payments/migrations work uses Sol medium. Failed Terra/Sol medium attempts escalate to Sol high and then Sol max; automatic routing never chooses Ultra.
  • Session-boundary invariants are checked deterministically before Codex starts and recorded only in the local audit log; they are not delegated to an LLM or repeated in the common prompt. A planner must have one source marker and run on the configured Sol-medium route; an execution issue must have one route marker, one planner approval marker, a different terminal planner source, and an allowed model. A worker proposal may finish only after the daemon re-reads or creates its linked Goal Planner, while the planner may finish only after naming its freshly created Goal-Approved Todo or recording an explicit root-goal termination reason.
  • Issues with verified evidence may move directly to Done; they do not accumulate in human review.
  • A valid negative result is also Done with a durable rejected evidence outcome. Workers never choose Canceled, Cancelled, or Duplicate; those states are reserved for humans or external systems.
  • Only a real Blocked condition requires human input during ordinary autonomous research and it posts a Linear comment that mentions the configured reviewer. Scheduled review is optional per profile, and live trading remains separately authorized.
  • The project description holds the big objective, the [Goal] issue holds measurable success criteria, and [Agent Goal Review] holds human maintain/adjust decisions.
  • Every accepted operator-feedback message becomes a durable [Human] issue in Todo. Loophony selects Human issues by Linear priority and creation time and creates a linked planning Work issue that runs as the Sol-medium Goal Planner. The planner decides whether the request advances the active goal and, only then, authors one Goal-Approved execution issue. The machine markers make this idempotent across daemon restarts. Explicit preemption returns the interrupted source issue to Todo, defaults the new request to Urgent when no priority is supplied, and preserves the source workspace for later resumption.
  • While work is running, Loophony keeps every semantic checkpoint and token/runtime observation in SQLite and the audit log. Linear preserves the Goal Planner issue, its source relation, bounded planning input, approved work contract, and immutable state-changing decisions such as terminal outcomes, blocks, rejections, and goal adjustments. Routine health checkpoints stay local.
  • Long external waits move the Linear issue from In Progress to the configured Waiting workflow state and release the Codex slot. Waiting means background observation: Loophony keeps the durable trigger and periodic checks active while independent code, research, or diagnosis continues. If no runnable Goal-Approved Todo remains, it creates one linked Sol-medium Goal Planner issue and promotes it so exactly one meaningful issue is In Progress. The planner must select a finite, evidence-producing task with a measurable goal delta; placeholder or keepalive work is invalid. The waiting issue returns to the Todo queue when its condition is ready. A successor-free planner exit is accepted only after every root success criterion and stop condition is proven. Long-running local commands can be launched as supervised jobs whose status, log, exit marker, and resume context survive daemon restarts.
  • A separate append-only SQLite audit ledger records operator, scheduler, checkpoint, wait, job, budget, goal-review, and memory-health transitions. Each event is secret-redacted and linked to the previous event with a versioned canonical SHA-256 format so offline edits are detectable.
  • Configurable issue/day token and active-runtime budgets warn before and after exhaustion without stopping work by default; explicit block and daily-reset wait policies remain available. The goal policy rejects ambiguous executable queues, missing goal versions, and work that does not map to the single active stage.

Orchestrator lifecycle

The orchestrator is the only scheduler. Linear is the durable control plane, SQLite stores waits, jobs, checkpoints, token usage, and the hash-chained audit trail, and Codex executes only the one issue selected by the orchestrator.

flowchart TB
    GOAL["Linear project objective<br/>Goal version + Active stage"] --> POLL
    HUMAN["Codex App feedback"] --> INTAKE["Create durable Human Todo<br/>preserve priority and age"]
    INTAKE --> POLL
    TODO["Goal-Approved Todo queue<br/>zero or more issues"] --> POLL
    WAITING["Waiting issues<br/>durable non-LLM triggers/jobs"] --> WATCH["Probe conditions without Codex"]
    WATCH -->|"ready"| TODO

    POLL["Poll and reconcile every 30 seconds"] --> SNAPSHOT["Read Linear candidates,<br/>runtime waits, and jobs"]
    SNAPSHOT --> INV{"Deterministic invariant check"}
    INV -->|"invalid"| FAILCLOSED["Do not start Codex<br/>record audit violation"]
    INV -->|"valid"| ACTIVE{"Exactly one In Progress?"}
    ACTIVE -->|"yes"| DISPATCH["Resume that issue"]
    ACTIVE -->|"no"| SELECT["Select eligible work<br/>Goal Planner first, then priority and age"]
    SELECT --> CLAIM["Promote one issue to In Progress"]
    CLAIM --> DISPATCH

    DISPATCH --> ROLE{"Session role"}
    ROLE -->|"Goal Planner marker"| GP["Sol Medium Goal Planner<br/>compact prompt, at most 2 turns"]
    GP --> REVIEW["Review previous work first<br/>claims vs tests, metrics, hashes, artifacts"]
    REVIEW --> REVIEWGATE{"Valid verify checkpoint?<br/>source ID + verdict + evidence"}
    REVIEWGATE -->|"no"| NOCREATE["Reject successor creation<br/>and terminal planner handoff"]
    REVIEWGATE -->|"yes"| NEXT{"Highest-value goal delta"}
    NEXT -->|"reuse"| TODO
    NEXT -->|"new bounded work"| CREATE["Create one linked<br/>Goal-Approved Todo"]
    CREATE --> TODO
    NEXT -->|"all criteria proven"| ROOTDONE["Record root_goal_complete<br/>with termination reason"]

    ROLE -->|"Execution or exploration"| ROUTE["Apply model routing policy"]
    ROUTE --> CODEX["Fresh top-level Codex session<br/>one bounded issue"]
    CODEX --> OUTCOME{"Durable outcome"}
    OUTCOME -->|"Done / Rejected"| TERMINAL["Verify evidence and terminal handoff"]
    TERMINAL --> PLANNER["Create or recover linked<br/>Goal Planner Todo"]
    PLANNER --> TODO
    OUTCOME -->|"Retry"| RETRY["Preserve workspace<br/>apply retry route/backoff"]
    RETRY --> POLL
    OUTCOME -->|"Waiting"| REGISTER["Persist wait/job binding<br/>release Codex slot"]
    REGISTER --> WAITING
    OUTCOME -->|"Blocked"| BLOCKED["Pause only for required<br/>human authority or input"]
    BLOCKED -->|"explicit unblock input"| INTAKE

    INV -.-> AUDIT["Append-only SQLite audit log"]
    REVIEWGATE -.-> AUDIT
    OUTCOME -.-> AUDIT
Loading

The important distinction is that Waiting is automated observation, not a human blocker. It releases the single Codex slot while the non-LLM runtime checks the condition, allowing unrelated goal work to continue.

Model routing

Goal planning and execution routing are separate. The Goal Planner judges the previous result and authors or selects a bounded contract; the selected execution profile then chooses the model for a fresh top-level session.

flowchart LR
    ISSUE["Selected Linear issue"] --> MARKER{"Goal Planner marker?"}
    MARKER -->|"yes"| GP["Goal Planner<br/>GPT-5.6 Sol Medium"]
    GP -->|"first failed attempt"| HIGH["GPT-5.6 Sol High"]
    HIGH -->|"repeated failed attempt"| MAX["GPT-5.6 Sol Max"]

    MARKER -->|"no"| PROFILE{"Explicit route profile?<br/>otherwise classify title + contract"}
    PROFILE -->|"mechanical"| SPARK["GPT-5.3 Codex Spark Medium"]
    PROFILE -->|"tiny_ui"| SPARK
    PROFILE -->|"normal"| SPARK
    SPARK -->|"failed attempt"| LUNA["GPT-5.6 Luna XHigh<br/>retry fallback"]

    PROFILE -->|"exploration"| TERRA["GPT-5.6 Terra Medium"]
    TERRA -->|"first failed attempt"| HIGH

    PROFILE -->|"complex"| SOL["GPT-5.6 Sol Medium"]
    SOL -->|"first failed attempt"| HIGH

    PROFILE -->|"explicit gpt54"| GPT54["GPT-5.4 High"]
    ULTRA["Sol Ultra"] -.->|"never selected automatically"| PROFILE
Loading

Every route writes model, reasoning_effort, route_profile, role, and source issue ID to the audit log. route=execution remains a compatibility alias for normal, and unknown profiles fail closed instead of silently choosing a model.

Set up from Codex App

Codex can perform the installation for you. The only manual boundaries are connector OAuth, local Keychain secret entry, and starting a new Codex task after new plugins are installed.

Prepare these non-secret values:

  • an existing Linear project URL or unambiguous project name;
  • your Linear reviewer handle;
  • the git clone URL of the repository where Loophony should do its work.

Do not paste a Linear API token, brokerage secret, or other credential into Codex or Linear.

1. Bootstrap Loophony

Open a new task in Codex App and paste this prompt:

Install Loophony on this Mac from the public repository
https://github.com/djm07073/loophony.

1. Install the repository's skills/loophony-setup skill in my Codex user skills directory.
2. Read the installed SKILL.md and continue following it in this task.
3. Safely clone the repository to ~/dev/agents/loophony, or reuse a clean matching clone.
4. Run preflight checks and install the Loophony, Linear, and Alpaca plugins.
5. If Linear or Alpaca OAuth is required, stop at the correct point and tell me how to connect it
   in Codex App.
6. Build and verify the Elixir daemon, but do not start the service yet.
7. Never request or print tokens or secrets in chat.

If a new Codex task is required to load the new plugins, give me the exact goal-creation prompt to
paste into that task.

Connect Linear in Codex App when prompted. Alpaca is optional unless the project needs its read-only market-data tools. Start a new Codex task after the plugins are installed.

2. Create the durable project goal

In the new task, replace the placeholders and paste:

$loophony-create-goal

Create the durable Loophony goal for this Linear project.

- Project: <LINEAR_PROJECT_URL_OR_EXACT_NAME>
- Desired change: <BROAD_OBJECTIVE>
- Important constraints: <CONSTRAINTS_OR_UNKNOWN>

Read the project and existing issues first. Research facts that you can verify independently.
Ask one question at a time only for decisions I must make, such as goals, scope, and tradeoffs.
Define the goal contract using observable outcomes, success criteria, evidence sources, non-goals,
authority boundaries, and conditions for achievement, rejection, or reframing—not activity volume.

Before writing, show me the draft and quality-gate result and obtain my approval.
After approval, create or update the project description's Loophony Goal block, the [Goal] root
issue, and the [Agent Goal Review] issue without creating duplicates.
Do not create an executable Candidate issue yet.

The skill returns the project slug, root goal issue, and persistent review issue identifier. Keep those values for the final setup prompt.

3. Seed the first loop

Goal provisioning deliberately does not invent an execution backlog. After approving the goal, paste this follow-up in the same task to create only the first bounded issue:

Create one planning seed for the highest-leverage first increment of the Loophony goal we just
approved. This seed is input to Loophony's Sol-medium Goal Planner, not an executable work issue.

Re-read the Linear project and [Goal] root issue, then select one unmet success criterion.
Create exactly one child planning-seed issue with these requirements:

- State: Candidate
- Label: symphony-quant
- Assignee: me
- Explicitly map it to at least one SC-* success criterion
- Include the observed unmet evidence and the expected measurable goal delta
- Include proposed acceptance checks, required evidence, risks, and non-goals
- Inherit the project's constraints, non-goals, and authority boundaries
- Do not duplicate completed or rejected work

Do not add a handoff or goal-approval marker; the daemon creates the Goal Planner and only that
planner may approve an executable issue. If the goal is already fully proven or no safe next
increment exists, do not create a seed; explain why. After creation, show the issue identifier,
URL, and mapped success criterion.

This is the only issue that normally needs manual seeding. Every later worker submits a proposal; the daemon opens a visible Sol-medium Goal Planner issue, and that planner alone chooses and writes the next execution issue.

4. Configure and start the daemon

Open a new task, replace the placeholders with the values returned above, and paste:

$loophony-setup

Continue configuring Loophony and start it as a 24/7 service.

- Linear project slug: <PROJECT_SLUG>
- Goal-review issue: <TEAM-123>
- Reviewer: <@HANDLE>
- Work repository clone URL: <GIT_CLONE_URL>

Render the configuration, build the daemon, and run its health check.
If a Linear API token is required, never accept it in chat. Give me only the command that lets me
enter it directly into Keychain from my local terminal. Warn me that existing Candidate or Ready
issues may run immediately. After I confirm that you should start, install the launchd service.
Finally, show the daemon status, next heartbeat, and current running, queued, Blocked, and review
gate states.

After health succeeds, use $loophony-control in Codex App to inspect or steer the daemon.

Ask Codex about prior loops

Loophony keeps retrieval and answer generation separate. Loophony normalizes documents and creates contextual, paragraph-aware sections; Onyx v4 owns ingestion, embeddings, and OpenSearch 3.6 keyword/vector hybrid retrieval. Its local model servers use intfloat/multilingual-e5-base (768 dimensions), so Korean questions search Korean and English evidence directly without translating or storing an English-only copy. Query expansion and Onyx LLM document selection stay disabled: Codex uses the loophony-query skill and Loophony's read-only MCP tools to synthesize the final evidence-backed answer.

The index contains seven provenance-preserving document types: the canonical linear_project objective, current linear_issue snapshots, derived session_summary rollups, raw checkpoint records, final agent messages, errors, and session lifecycle events. The project objective is stored once under a stable project ID instead of being repeated in every issue chunk. Project and issue snapshots are updated after tracker reads; unchanged content is not re-embedded again during the same daemon runtime. Session summaries are generated deterministically at turn completion from that session's checkpoints and recent final messages. They help navigation but do not replace the raw records used to verify claims.

RAG document-processing policy

  • Preserve raw evidence and create summaries as separate derived documents. Do not make a lossy summary the only searchable record.
  • Use stable source IDs, schema versions, update timestamps, and content hashes so changed records upsert predictably and unchanged records can be skipped.
  • Chunk on paragraph boundaries (up to 1,200 characters here), let Onyx enforce its token-aware limit, and prepend project, evidence type, issue, session, timestamp, and title context to every submitted section. The active E5 model truncates inputs beyond 512 tokens, so large character-only chunks are unsafe for multilingual text.
  • Store filterable provenance separately from searchable text, then apply issue, session, type, and time filters before Codex writes an answer.
  • Evaluate retrieval independently with a small set of real Korean and English progress, decision, failure, and chronology questions; measure whether the supporting evidence appears in the top results before evaluating answer prose.

Silent-death protection

Loophony uses independent liveness layers rather than treating an In Progress Linear state as proof that work is alive:

  • The orchestrator polls every 30 seconds and measures silence from the latest Codex app-server event. The quant profile restarts a silent worker after 10 minutes while preserving its workspace.
  • A running issue receives an append-only ## Loophony Health comment every 15 minutes. It records the observation time, daemon boot ID, worker state, session, latest event time, silence duration, restart threshold, and next heartbeat in both UTC and KST.
  • Routine semantic loop checkpoints stay in SQLite and the audit log. State-changing checkpoints are appended as separate ## Loophony Decision comments. Identical retries are deduplicated by a content fingerprint, while changed decision evidence remains visible as a new immutable revision.
  • A detected silent stall appends a timestamped health event before the worker is restarted.
  • Memory health uses a real hybrid-search canary, tracks search and ingestion separately, and opens a short circuit breaker after repeated failures instead of reporting a healthy socket as healthy retrieval.
  • On macOS, the independent launchd watchdog probes the loopback API every 60 seconds and restarts the daemon only after two consecutive failures, so one transient timeout does not interrupt work.
  • A separate Codex continuity maintenance job runs every 60 minutes. Healthy runs exit with a healthy/no-op record; repeated orchestration defects may be repaired with a regression test, build, service installation/restart, and a post-install health check. An active Codex session defers service replacement; detached durable jobs remain running and are verified before and after the daemon restart.

These choices follow current guidance on cleaning, metadata, semantic chunking, incremental updates, and retrieval evaluation from Microsoft's advanced RAG guide, the context-prepending and hybrid retrieval findings in Anthropic's Contextual Retrieval, Onyx's stable document/section model, and OpenSearch's rank evaluation API. The model-specific limit comes from the multilingual-e5-base model card.

Start the local retrieval dependencies before enabling memory in the rendered workflow:

python3 elixir/scripts/onyx_bootstrap.py

Then ask in Codex App, for example, “지난 세션들이 인증 마이그레이션을 포기한 이유가 뭐야?” The answer cites exact issue, session, and evidence identifiers. The Onyx administrator token is stored in macOS Keychain and is available only to the Loophony daemon; the Codex plugin exposes a smaller read-only search surface rather than direct administrative access.

Example: a quant research goal

Assume the Linear project is Quant Research Lab and the initial request is vague:

Continuously research US equity signals and find profitable strategies.

That is an activity, not a finishable goal. $loophony-create-goal asks about the baseline, decision the system must enable, universe, evidence standard, authority, and stopping conditions. For example, the short shaping dialogue might be:

Codex: What is the current baseline that should change?
User: Data and notebooks exist, but results cannot be reproduced by a fresh session.

Codex: What decision must the finished system make reliably?
User: It must accept or reject a signal hypothesis under predefined out-of-sample and cost gates.

Codex: What authority is explicitly outside the system?
User: No live orders or spending. Read-only and paper data only.

The resulting contract could look like this:

Outcome: Build a reproducible US-equity research system that can accept or reject signal
hypotheses using predeclared out-of-sample, cost, liquidity, and capacity gates without live
trading.

SC-01 — A point-in-time dataset can be rebuilt from an immutable snapshot
        | deterministic hash and data-quality checks pass
        | dataset manifest and CI report

SC-02 — The backtest harness detects injected look-ahead and survivorship leakage
        | all adversarial leakage fixtures fail closed
        | test report and committed fixtures

SC-03 — Every evaluated hypothesis produces a reproducible accept or reject decision
        | walk-forward result includes fees, spread, slippage, turnover and capacity assumptions
        | versioned research package linked from Linear

Non-goals: live orders, guaranteed returns, unrestricted universe expansion.
Authority: read-only or paper data only; credentials never enter Linear or prompts.
Achieved: all three contracts have repeatable evidence and can be operated by a fresh session.
Reframe: required data is unavailable or the evidence gates cannot answer the intended decision.

Loophony then turns the contract into bounded work, one issue at a time:

  1. Codex App seeds QRL-101 — Build immutable point-in-time dataset manifest, mapped to SC-01.
  2. Loophony opens a distinct [Goal Planner][SC-01] issue on Sol medium. The planner first verifies the seed's claimed outcome, then compares the root goal with the injected compact queue and authors Goal-Approved QRL-101 only if its expected goal delta justifies another cycle.
  3. One routed Codex session executes only QRL-101, records checkpoints in SQLite and the audit log, commits reproducible artifacts, and leaves a structured next-cycle proposal. The daemon creates another visible Goal Planner issue before QRL-101 finishes.
  4. A later fresh session evaluates a signal hypothesis for SC-03. A correctly reproduced negative result may finish that issue as Rejected; it is not treated as an agent failure.
  5. The user may inspect Linear at any time and submit feedback. Every accepted request becomes a [Human] Todo issue, so the user can see exactly which ticket Loophony will handle. Loophony claims the highest-priority oldest Human issue and creates a linked planning Work issue. Sol medium first reviews the source request and decides how it advances the active goal; a selected task then runs on its routed execution profile. The Human source remains Todo until the complete downstream handoff chain finishes. Ordinary feedback stays queued without disturbing active work. Only an explicit preemption interrupts the active Codex turn and preserves its workspace before scheduling resumes.

The full state transition, including Goal Planner review, Waiting, retries, and routing, is shown in the lifecycle diagrams above.

How a fresh session recovers context

A new loop does not depend on hidden chat memory. It reconstructs its context from:

  • the Linear project description and root [Goal] success criteria;
  • the current issue description, relations, acceptance checks, workpad, and human comments;
  • repository files, git history, tests, datasets, and published artifacts;
  • only the current issue's recent SQLite checkpoints.

Cross-issue knowledge must be handed off explicitly through the next issue and linked artifacts. The agent re-checks every candidate issue against the active goal before running it; misaligned work is narrowed or rejected instead of silently consuming another loop.

Manual installation

To install only the standalone bootstrap skill:

python3 ~/.codex/skills/.system/skill-installer/scripts/install-skill-from-github.py \
  --repo djm07073/loophony \
  --path skills/loophony-setup

To install only the public plugin:

/Applications/Codex.app/Contents/Resources/codex plugin marketplace add djm07073/loophony
/Applications/Codex.app/Contents/Resources/codex plugin add loophony@loophony-public

Upstream Symphony

The original Symphony design turns project work into isolated agent runs. This fork keeps the official Elixir orchestrator as its base and adds the Linear goal contract, issue-scoped SQLite loop memory, queue and heartbeat rules, scheduled human goal review, and the Loophony Codex plugin.

For the upstream specification and reference implementation, see:

License

This project is licensed under the Apache License 2.0.

About

Durable goal-oriented agent orchestration based on OpenAI Symphony

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages