Skip to content

feat: Add OpenCode Backend Support with Performance Instrumentation & LinkedIn Optimizations#20

Draft
rothnic wants to merge 25 commits intoPickle-Pixel:mainfrom
rothnic:feat/multi-backend-support
Draft

feat: Add OpenCode Backend Support with Performance Instrumentation & LinkedIn Optimizations#20
rothnic wants to merge 25 commits intoPickle-Pixel:mainfrom
rothnic:feat/multi-backend-support

Conversation

@rothnic
Copy link

@rothnic rothnic commented Feb 26, 2026

feat: Add OpenCode Backend Support with Performance Instrumentation

Adds OpenCode CLI as an alternative backend to Claude Code for auto-apply orchestration.

Usage:

export APPLY_BACKEND=opencode
applypilot apply --url "https://linkedin.com/jobs/view/..."

Claude Code remains the default. OpenCode is opt-in via APPLY_BACKEND=opencode.

What's Included

  • OpenCode backend - JSON streaming protocol, structured logging, unified status taxonomy
  • Performance instrumentation - JSONL audit logs with per-action timestamps and LLM latency tracking
  • LinkedIn optimizations - Fast-path Easy Apply instructions, overlay bypass for #interop-outlet
  • Resume upload fix - Copy resume to worker directory for proper file access
  • Pre-navigation - Chrome CDP HTTP endpoint for persistent tab creation
  • analyze command - Post-run performance review via CLI

OpenCode Configuration Approach

Project Mode with XDG Isolation:

  • Configuration: ~/.applypilot/.opencode/opencode.jsonc
  • Custom agent: applypilot-apply defined in config
  • XDG_CONFIG_HOME redirected to ~/.applypilot (isolates from global ~/.config/opencode/)
  • Auth credentials preserved at ~/.opencode/auth.json (unaffected by redirect)

Custom Agent Definition:

{
  "agent": {
    "applypilot-apply": {
      "description": "Autonomous job application agent",
      "mode": "primary",
      "model": "github-copilot/gpt-5-mini",
      "prompt": "{file:../prompts/apply-agent.md}",
      "permission": {
        // Safety: deny file editing and bash
        "task": "deny",
        "edit": "deny",
        "write": "deny",
        "bash": "deny",
        // Read-only tools allowed
        "read": "allow",
        "grep": "allow",
        "glob": "allow",
        "lsp": "allow",
        // All Playwright browser tools enabled
        "playwright_browser_navigate": "allow",
        "playwright_browser_click": "allow",
        "playwright_browser_fill_form": "allow",
        "playwright_browser_snapshot": "allow",
        "playwright_browser_evaluate": "allow",
        "playwright_browser_file_upload": "allow",
        "playwright_browser_tabs": "allow",
        "playwright_browser_wait_for": "allow",
        "playwright_browser_screenshot": "allow",
        // All Gmail tools enabled
        "gmail_search_emails": "allow",
        "gmail_read_email": "allow",
        "gmail_send_email": "allow",
        "gmail_create_draft": "allow"
      }
    }
  }
}

Why This Approach:

  • Prevents ApplyPilot's agent config from interfering with personal OpenCode workflows
  • Allows per-project customization without polluting global config
  • Keeps authentication separate from configuration
  • Enables reproducible, isolated agent behavior

Instrumentation

Captures detailed timing data:

  • LLM response time per turn
  • Tool execution time (browser actions)
  • Idle gaps between actions
  • Token usage

Logs written to:

  • worker-{id}.events.jsonl - Structured action history
  • worker-{id}.perf.jsonl - Performance timing analysis

Observations

From instrumented runs:

  • Chrome start → first response: 15-25s
  • LLM response time: 5-20s per turn (variable)
  • Tool execution: <200ms consistently
  • LinkedIn #interop-outlet blocks ~30% of Playwright clicks
  • Resume upload verification loops caused 30+ second delays

Files Changed (12 total)

Core Implementation (4 files)

File Purpose
src/applypilot/apply/backends.py OpenCodeBackend, JSON parsing, instrumentation
src/applypilot/apply/launcher.py Pre-navigation, resume path handling
src/applypilot/apply/prompt.py LinkedIn fast-path, existence-based workflows
src/applypilot/cli.py analyze command

Supporting Changes (4 files)

File Purpose
src/applypilot/config.py Backend configuration
src/applypilot/wizard/init.py Setup wizard updates
.env.example Environment variable documentation
README.md Documentation updates

Tests (4 files)

File Purpose
tests/test_backend_selection.py Backend selection tests
tests/test_parser_edge_cases.py Parser edge case tests
tests/conftest.py Test configuration
tests/__init__.py Test package init

Future Optimization Opportunities

See section below for potential enhancements: custom tools, CLI optimizations, alternative browser automation (Camofox, agent-browser), MCP client direct connection, and platform-specific workflows.


Future Optimization Opportunities

This PR Is Intentionally Limited

This PR focuses solely on OpenCode integration and making it functional. The following optimizations are identified but intentionally left for future PRs to keep scope manageable:

1. Custom Tool Integration via OpenCode

OpenCode supports custom tools via MCP (Model Context Protocol). Potential optimizations:

  • Domain-Specific Tools: Custom tools for LinkedIn/Indeed/Workday that understand platform structures
  • Fast Selectors: Pre-computed selectors for common actions ("Find Easy Apply button", "Click Continue")
  • State Machines: Tools that encode platform-specific workflows as state transitions
  • Validation Tools: Fast verification tools that return structured data vs parsing HTML

2. CLI Tools with Stored Optimizations

Build CLI tools that leverage accumulated knowledge:

  • Selector Database: Store working selectors per platform, update as UI changes
  • Workflow Templates: Platform-specific step sequences (LinkedIn: Easy Apply → Contact → Resume → Questions → Review)
  • Timing Profiles: Platform-specific wait times and retry patterns
  • Fallback Chains: Ordered list of strategies when primary approach fails

3. Alternative Browser Automation

Current: Playwright via MCP. Alternatives to explore:

  • Playwright CLI: Direct Playwright usage without MCP layer
  • Agent-Browser: Purpose-built browser automation for agents
  • Camofox: Firefox-based automation with enhanced stealth (avoid detection)
  • Puppeteer: Chrome DevTools Protocol direct control

4. MCP Client Direct Connection

Instead of agent-managed MCP, use programmatic MCP client:

  • Direct Server Connection: Connect to Playwright MCP server directly from backend code
  • Batched Operations: Group multiple actions into single MCP calls
  • Context Persistence: Maintain browser context across operations without agent re-initialization
  • Reduced Overhead: Skip agent reasoning for known action sequences

5. Cached Analysis

  • Page Pattern Cache: Store DOM structure patterns for known platforms
  • Resume Parsing Cache: Cache parsed resume data across applications
  • Job Analysis Cache: Avoid re-analyzing similar job descriptions

6. Parallel Execution

  • Multi-Worker: Run multiple applications simultaneously
  • Pipeline Architecture: Separate collection, analysis, and application phases

Migration

No migration required. Existing Claude Code users unaffected.

To try OpenCode:

export APPLY_BACKEND=opencode
applypilot apply --dry-run --url "https://linkedin.com/jobs/view/..."

Checklist

  • OpenCode backend implementation
  • JSONL audit logging with timestamps
  • Performance instrumentation
  • Pre-navigation fix (Chrome CDP)
  • Resume upload path fix
  • LinkedIn Easy Apply optimizations
  • Modal overlay bypass
  • analyze CLI command
  • Merge conflicts resolved
  • Python syntax validation
  • PR description updated with abstractions and observations

Architecture Principle: Instrument first, optimize second. This PR establishes comprehensive instrumentation to measure before optimizing.

Add unified AgentBackend abstraction supporting both Claude Code and OpenCode:
- AgentBackend abstract base class with run_job() interface
- ClaudeBackend implementation for Claude Code CLI
- OpenCodeBackend implementation for OpenCode CLI with MCP support
- Backend detection and auto-selection logic
- Backend-specific configuration in .env.example
- Comprehensive tests for backend selection and output parsing
- Wizard integration for backend setup and configuration
- Launcher updates to use new backend abstraction

This enables users to choose between Claude Code (default) and OpenCode
for autonomous job application submission.
@rothnic rothnic force-pushed the feat/multi-backend-support branch from 53c05f4 to 9a76346 Compare February 27, 2026 17:14
Remove unused imports:
- conftest.py: os, Path, MagicMock
- test_backend_selection.py: os
- test_parser_edge_cases.py: time, PropertyMock
- Revert .env.example to match origin/main
- Add only OpenCode-related configuration options:
  - APPLY_BACKEND selection
  - APPLY_CLAUDE_MODEL, APPLY_OPENCODE_MODEL, APPLY_OPENCODE_AGENT
  - OpenCode MCP baseline requirements
- Add scripts/test_opencode_apply.py for end-to-end testing
  - Checks prerequisites (OpenCode binary, MCP servers)
  - Runs applypilot apply --dry-run --url <job-url>
  - Provides clear output about what worked/failed
Add prerequisite checks for:
- ~/.applypilot/profile.json (run applypilot init to create)
- ~/.applypilot/resume.txt or resume.pdf

These are required for applypilot apply to work.
Add --backend/-b option to explicitly select backend (claude or opencode).
This makes it easier to test OpenCode without relying solely on environment variables.

Changes:
- Add --backend/-b CLI option to apply command
- Display selected backend in output
- Pass backend_name to apply_main launcher function

Usage:
    applypilot apply --backend opencode --url https://...
    applypilot apply -b opencode --dry-run
@rothnic rothnic force-pushed the feat/multi-backend-support branch from c166b8f to cc14e8b Compare February 27, 2026 18:57
SQL query 'apply_status != 'in_progress'' doesn't match NULL values.
Changed to '(apply_status IS NULL OR apply_status != 'in_progress')'
to properly find jobs that haven't been attempted yet.
OpenCode looks for .opencode/opencode.jsonc in the current working directory.
Updated _list_mcp_servers() and run_job() to use config.APP_DIR (~/.applypilot)
as the cwd so OpenCode can find the MCP server configuration.
OpenCode installs to ~/.opencode/bin/opencode by default, which may not be
on PATH. Updated _find_binary to check this location if not found on PATH.
OpenCode installs to ~/.opencode/bin/opencode by default, which may not be
on PATH. Updated _find_binary to check this location if not found on PATH.
…CONTENT

- OpenCode expects prompt as positional argument, not via stdin
- Add OPENCODE_CONFIG_CONTENT env var to disable interactive prompts
- Pre-approve permissions to prevent 'question' tool from hanging
- Fixes 'Session not found' and early termination issues
- Remove OPENCODE_CLIENT, OPENCODE_SERVER_PASSWORD, etc.
- Ensure PATH includes ~/.opencode/bin
- Set TERM for proper terminal handling
- Fixes 'Session not found' error
- Add DEBUG LOGGING section to report progress at each step
- Provide specific guidance for finding Apply buttons on Workday, Lever, Greenhouse
- Add browser_evaluate snippet to search for apply buttons dynamically
…rolling

- Add browser_evaluate step to get ALL interactive elements first
- Emphasize using browser_evaluate over visual scanning
- Add 'NEVER SCROLL manually' instruction
- browser_snapshot is now for text content only, not element positions
- Use browser_click with text references, not coordinates
- Add step-by-step upload: (1) click upload button, (2) call browser_file_upload
- Emphasize that clicking FIRST is required to trigger file chooser modal
- Add Workday-specific upload guidance
- Include explicit JSON syntax for browser_file_upload call
- Add MCP TOOL EXAMPLES section with exact syntax
- Show 2-step upload workflow: click button, then browser_file_upload
- Include exact JSON syntax for paths parameter
- Add examples for fill_form, click, navigate, evaluate
- Fix syntax error in fill_form example
- Remove complex JavaScript examples that caused syntax errors
- Replace with simple text descriptions of tool usage
- Change 'NEVER SCROLL' to 'SCROLL ONLY WHEN NECESSARY'
- Add form field discovery to step 2
@i7opscom
Copy link

i7opscom commented Mar 1, 2026

Does this have failover for applying with claude when spending cap reached? Have you tested other models for applying, do they all handle this equally well?

@rothnic
Copy link
Author

rothnic commented Mar 1, 2026

At the moment, I'm implementing this focused on extending the coding agent abstraction with a switch to use opencode rather than claude code. It doesn't yet have a fallback, but it wouldn't be too difficult to support, though would do that as a follow up.

I currently have this working, but need to push some recent changes. I saw the other PR to add litellm support, so I pulled out the openai compatible api support from this PR, and am focused only on the opencode support/coding agent abstraction.

Overall issues i'm seeing right now, is just that the prompt.py isn't really adequate, though at the moment I am using a very cheap model (gpt-5-mini) for testing that isn't as smart as the default one used. For example, the job I'm using for testing the application dry run wanted me to sign in with linkedin, but didn't seem to try to the option to click the continue with google button, it seemed to only want to use username and password. I signed into the browser with google so it would have easy access to social sign in, but it was explicitly told to only use username and password. I think the current challenge is we really need multiple well defined tests that test out the agent applying for positions across multiple sites that have different characteristics

I do think that the current implementation of the agentic application is somewhat slow and could be significantly faster with better tooling, skills focused on particular families of sites, alternative browsers that would be less likely to have to solve captchas (camofox) or more efficient cli options (agent-browser), or code-mode, combined with config-driven custom tools. Ideally, when the agent is browsing it would more proactive information about the site and would avoid so much slow back and forth playwright tools. However, at the moment, my main focused for this PR is just adding the opencode support and any prompt optimizations to make sure it works for me and I'm pretty much there now.

rothnic added 2 commits March 1, 2026 04:19
…on and LinkedIn optimizations

- Add JSONL audit logging with timestamps and 30-day retention
- Implement performance instrumentation to track LLM API latency
- Fix pre-navigation to use Chrome CDP HTTP endpoint for persistent tabs
- Fix resume upload path to copy file to worker directory
- Add LinkedIn Easy Apply fast-path instructions (5-second target)
- Add LinkedIn modal handling with overlay bypass for #interop-outlet
- Add analyze CLI command for reviewing agent run performance
- Update prompts with existence-based action workflows
@rothnic rothnic changed the title feat: Add OpenCode backend + OpenAI-compatible endpoints + task-specific models feat: Add OpenCode Backend Support with Performance Instrumentation & LinkedIn Optimizations Mar 1, 2026
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