본문 바로가기
프로젝트

[brain] 하네스 엔지니어링(에이전트) 기록 -1-

by ISA(류) 2026. 6. 27.

mvp

솔직히 이거랑 평범하게 API 호출하는 앱이랑 무슨 차이인지 잘모르겠다. 솔직히 기술적으로 큰 차이를 못느끼겠다. 아무래도 LLM 이라 벡엔드 퀄리티가 높으니 재미는 있긴한데... 모델튜닝도 마찬가지고 퀄리티 좋게하는게 노가다라는건 늘 그랬고... 

 

머릿속에 그리던거랑 큰차이 없긴한데 실제로 해보는 것이 의미가 있긴하니까 재미 있는 장난감이라 생각하고 꾸준히 진행해봐야겠다. 역시 날로 먹는건 어렵구나. 특별한건 없고 간단하게 스킬 구현하고 동기식 루프 정도?; 기본적으로 다 교체 가능하게 구조 짜놨으니 LLM 자동 스위칭이나 스킬 작성 개선, 같은 기능들 구현하면 헤르메스나 요즘 새로 출시된 아키텍처인데.. 솔직히 여기에 뭐 특별한게 있나? 아이디어도 평범하고 기술도 평범하다. 그저 노가다로 올린 퀄리티가 유의미할뿐.

이를 활용해서 코드를 작성해보면

[2026-06-27T22:32:13.824Z] [INFO] [App Initialization] Starting Brain Harness Application Engine...
[2026-06-27T22:32:13.828Z] [INFO] [App Initialization] Initializing Configuration-Driven Model Managers...
[2026-06-27T22:32:13.828Z] [INFO] [App Initialization] Loaded Agent Skills: [file, run]
[2026-06-27T22:32:13.828Z] [INFO] [App Initialization] Brain Harness MVP Core System Ready.
[2026-06-27T22:35:17.047Z] [INFO] [User Request Ingested] Swapping context to [Role: coder] | Data: {
  "userInput": "./main.js 파일을 만들어서 react +canvas로 비오는날 연못을 보여주는 로직을 작성해줘"
}
[2026-06-27T22:35:17.051Z] [INFO] [Harness Started] Initializing task for role: coder | Data: {
  "templateId": "code-generation"
}
[2026-06-27T22:36:04.976Z] [INFO] [Harness Success] Output cleared guardrails successfully. | Data: {
  "roleId": "coder",
  "retryCount": 0
}
[2026-06-27T22:36:04.976Z] [INFO] [Tool Call Intercepted] Model requested action.
[2026-06-27T22:36:04.977Z] [INFO] [Tool Execution] Executing Skill: file -> writeFileAsync
[2026-06-27T22:36:04.979Z] [INFO] [Tool Execution] Feeding results back to model for next step.
[2026-06-27T22:36:04.979Z] [INFO] [User Request Ingested] Swapping context to [Role: coder] | Data: {
  "userInput": "[TOOL EXECUTION RESULTS]\n[\n  {\n    \"skill\": \"file\",\n    \"function\": \"writeFileAsync\",\n    \"success\": true\n  }\n]\n\nReview these results and next task."
}
[2026-06-27T22:36:04.981Z] [INFO] [Harness Started] Initializing task for role: coder | Data: {
  "templateId": "code-generation"
}
[2026-06-27T22:36:20.187Z] [INFO] [Harness Success] Output cleared guardrails successfully. | Data: {
  "roleId": "coder",
  "retryCount": 0
}
[2026-06-27T22:38:13.717Z] [INFO] [User Request Ingested] Swapping context to [Role: coder] | Data: {
  "userInput": "이걸 설치 없이 간단히 확인해볼려면 무슨 사이트를 활용하는게 좋을까?"
}
[2026-06-27T22:38:13.719Z] [INFO] [Harness Started] Initializing task for role: coder | Data: {
  "templateId": "code-generation"
}
[2026-06-27T22:38:38.011Z] [INFO] [Harness Success] Output cleared guardrails successfully. | Data: {
  "roleId": "coder",
  "retryCount": 0
}

간단한 작업을 시키고 로그를 긁어와봤다. 작성한 코드는 아래와 같다.

import React, { useEffect, useRef } from 'react';

const RainyPond = () => {
  const canvasRef = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    let animationFrameId;

    // Resize canvas to fill window
    const resizeCanvas = () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
    };

    window.addEventListener('resize', resizeCanvas);
    resizeCanvas();

    // Constants
    const rainCount = 150;
    const pondY = canvas.height * 0.7; // Pond starts at 70% of height
    const rainParticles = [];
    const ripples = [];

    // Rain Particle Class
    class RainParticle {
      constructor() {
        this.init();
      }

      init() {
        this.x = Math.random() * canvas.width;
        this.y = Math.random() * -canvas.height;
        this.length = Math.random() * 20 + 10;
        this.speed = Math.random() * 10 + 10;
        this.opacity = Math.random() * 0.5 + 0.2;
      }

      draw() {
        ctx.beginPath();
        ctx.strokeStyle = `rgba(174, 194, 224, ${this.opacity})`;
        ctx.lineWidth = 1;
        ctx.moveTo(this.x, this.y);
        ctx.lineTo(this.x, this.y + this.length);
        ctx.stroke();
      }

      update() {
        this.y += this.speed;

        // Collision with pond surface
        if (this.y >= pondY) {
          ripples.push(new Ripple(this.x, pondY));
          this.init();
          this.y = -this.length; // Reset to top
        }

        // Reset if it goes off screen (though pond usually catches it)
        if (this.y > canvas.height) {
          this.init();
        }
      }
    }

    // Ripple Class
    class Ripple {
      constructor(x, y) {
        this.x = x;
        this.y = y;
        this.radius = 0;
        this.maxRadius = Math.random() * 30 + 20;
        this.opacity = 0.5;
        this.speed = 0.8;
      }

      draw() {
        ctx.beginPath();
        ctx.strokeStyle = `rgba(255, 255, 255, ${this.opacity})`;
        ctx.lineWidth = 1;
        ctx.ellipse(this.x, this.y, this.radius, this.radius * 0.3, 0, 0, Math.PI * 2);
        ctx.stroke();
      }

      update() {
        this.radius += this.speed;
        this.opacity -= 0.01;
      }
    }

    // Initialize rain
    for (let i = 0; i < rainCount; i++) {
      rainParticles.push(new RainParticle());
    }

    const render = () => {
      // Background: Sky and Pond
      const skyGradient = ctx.createLinearGradient(0, 0, 0, pondY);
      skyGradient.addColorStop(0, '#1a1a2e');
      skyGradient.addColorStop(1, '#16213e');
      ctx.fillStyle = skyGradient;
      ctx.fillRect(0, 0, canvas.width, pondY);

      const pondGradient = ctx.createLinearGradient(0, pondY, 0, canvas.height);
      pondGradient.addColorStop(0, '#0f3460');
      pondGradient.addColorStop(1, '#051937');
      ctx.fillStyle = pondGradient;
      ctx.fillRect(0, pondY, canvas.width, canvas.height - pondY);

      // Update and draw ripples
      for (let i = ripples.length - 1; i >= 0; i--) {
        ripples[i].update();
        ripples[i].draw();
        if (ripples[i].opacity <= 0) {
          ripples.splice(i, 1);
        }
      }

      // Update and draw rain
      rainParticles.forEach(particle => {
        particle.update();
        particle.draw();
      });

      animationFrameId = requestAnimationFrame(render);
    };

    render();

    return () => {
      window.removeEventListener('resize', resizeCanvas);
      cancelAnimationFrame(animationFrameId);
    };
  }, []);

  return (
    <canvas
      ref={canvasRef}
      style={{
        display: 'block',
        backgroundColor: '#000',
        margin: 0,
        padding: 0,
        overflow: 'hidden',
        width: '100vw',
        height: '100vh'
      }}
    />
  );
};

export default RainyPond;

파일 같은 것을 읽은 내용물이 필요하면 피드백루프를 돌리고, 단순히 여러 스킬을 순차적으로 호출해야하면 스킬루프를 돌리는 구조.

반응형