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.
- 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
Todoissues, but only one executable issue may beIn 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:v2routing andloophony-goal-approval:v1provenance 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
Donewith a durablerejectedevidence outcome. Workers never chooseCanceled,Cancelled, orDuplicate; those states are reserved for humans or external systems. - Only a real
Blockedcondition 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 inTodo. 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 Progressto the configuredWaitingworkflow 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 isIn 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
blockand daily-resetwaitpolicies remain available. The goal policy rejects ambiguous executable queues, missing goal versions, and work that does not map to the single active stage.
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
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.
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
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.
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.
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.
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.
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.
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.
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.
- 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.
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 Healthcomment 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 Decisioncomments. 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-oprecord; 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.pyThen 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.
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:
- Codex App seeds
QRL-101 — Build immutable point-in-time dataset manifest, mapped toSC-01. - 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-ApprovedQRL-101only if its expected goal delta justifies another cycle. - 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 beforeQRL-101finishes. - A later fresh session evaluates a signal hypothesis for
SC-03. A correctly reproduced negative result may finish that issue asRejected; it is not treated as an agent failure. - 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.
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.
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-setupTo 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-publicThe 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:
This project is licensed under the Apache License 2.0.